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

Java tutorial

Introduction

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

Source

/*
 * Copyright 2011 the original author or authors.
 *
 * 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 java.io.IOException;
import java.io.Writer;
import java.util.Stack;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.groovy.grails.support.encoding.EncodedAppenderWriterFactory;
import org.codehaus.groovy.grails.support.encoding.Encoder;
import org.codehaus.groovy.grails.support.encoding.EncodingStateRegistry;
import org.codehaus.groovy.grails.web.servlet.mvc.GrailsWebRequest;
import org.codehaus.groovy.grails.web.util.CodecPrintWriter;
import org.codehaus.groovy.grails.web.util.GrailsLazyProxyPrintWriter;
import org.codehaus.groovy.grails.web.util.GrailsLazyProxyPrintWriter.DestinationFactory;
import org.codehaus.groovy.grails.web.util.GrailsWrappedWriter;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

public final class GroovyPageOutputStack {

    public static final Log log = LogFactory.getLog(GroovyPageOutputStack.class);

    private static final String ATTRIBUTE_NAME_OUTPUT_STACK = "org.codehaus.groovy.grails.GSP_OUTPUT_STACK";

    public static GroovyPageOutputStack currentStack() {
        return currentStack(true);
    }

    public static GroovyPageOutputStack currentStack(GrailsWebRequest request) {
        return currentStack(request, true);
    }

    public static GroovyPageOutputStack currentStack(boolean allowCreate) {
        return currentStack(GrailsWebRequest.lookup(), allowCreate);
    }

    public static GroovyPageOutputStack currentStack(GrailsWebRequest request, boolean allowCreate) {
        if (allowCreate) {
            return currentStack(request, allowCreate, null, allowCreate, false);
        }
        return lookupStack(request);
    }

    public static GroovyPageOutputStack currentStack(boolean allowCreate, Writer topWriter, boolean autoSync,
            boolean pushTop) {
        return currentStack(GrailsWebRequest.lookup(), allowCreate, topWriter, autoSync, pushTop);
    }

    public static GroovyPageOutputStack currentStack(GrailsWebRequest request, boolean allowCreate,
            Writer topWriter, boolean autoSync, boolean pushTop) {
        return currentStack(new GroovyPageOutputStackAttributes.Builder().webRequest(request)
                .allowCreate(allowCreate).topWriter(topWriter).autoSync(autoSync).pushTop(pushTop).build());
    }

    public static GroovyPageOutputStack currentStack(GroovyPageOutputStackAttributes attributes) {
        GroovyPageOutputStack outputStack = lookupStack(attributes.getWebRequest());
        if (outputStack != null) {
            if (attributes.isPushTop()) {
                outputStack.push(attributes, false);
            }
            return outputStack;
        }

        if (attributes.isAllowCreate()) {
            if (attributes.getTopWriter() == null) {
                attributes = new GroovyPageOutputStackAttributes.Builder(attributes)
                        .topWriter(lookupRequestWriter(attributes.getWebRequest())).build();
            }
            return createNew(attributes);
        }

        return null;
    }

    private static final GroovyPageOutputStack createNew(GroovyPageOutputStackAttributes attributes) {
        GroovyPageOutputStack instance = new GroovyPageOutputStack(attributes);
        attributes.getWebRequest().setAttribute(ATTRIBUTE_NAME_OUTPUT_STACK, instance,
                RequestAttributes.SCOPE_REQUEST);
        return instance;
    }

    private static GroovyPageOutputStack lookupStack(GrailsWebRequest webRequest) {
        GroovyPageOutputStack outputStack = (GroovyPageOutputStack) webRequest
                .getAttribute(ATTRIBUTE_NAME_OUTPUT_STACK, RequestAttributes.SCOPE_REQUEST);
        return outputStack;
    }

    public static final void removeCurrentInstance() {
        RequestContextHolder.currentRequestAttributes().removeAttribute(ATTRIBUTE_NAME_OUTPUT_STACK,
                RequestAttributes.SCOPE_REQUEST);
    }

    public static final Writer currentWriter() {
        GroovyPageOutputStack outputStack = currentStack(false);
        if (outputStack != null) {
            return outputStack.getOutWriter();
        }

        return lookupRequestWriter();
    }

    private static Writer lookupRequestWriter() {
        GrailsWebRequest webRequest = GrailsWebRequest.lookup();
        return lookupRequestWriter(webRequest);
    }

    private static Writer lookupRequestWriter(GrailsWebRequest webRequest) {
        if (webRequest != null) {
            return webRequest.getOut();
        }
        return null;
    }

    private Stack<StackEntry> stack = new Stack<StackEntry>();
    private GroovyPageProxyWriter taglibWriter;
    private GroovyPageProxyWriter outWriter;
    private GroovyPageProxyWriter staticWriter;
    private GroovyPageProxyWriter expressionWriter;
    private boolean autoSync;
    private EncodingStateRegistry encodingStateRegistry;
    private GroovyPageProxyWriterGroup writerGroup = new GroovyPageProxyWriterGroup();

    private static class StackEntry implements Cloneable {
        Writer originalTarget;
        Writer unwrappedTarget;
        Encoder staticEncoder;
        Encoder taglibEncoder;
        Encoder defaultTaglibEncoder;
        Encoder outEncoder;
        Encoder expressionEncoder;

        StackEntry(Writer originalTarget, Writer unwrappedTarget) {
            this.originalTarget = originalTarget;
            this.unwrappedTarget = unwrappedTarget;
        }

        @Override
        public StackEntry clone() {
            StackEntry newEntry = new StackEntry(originalTarget, unwrappedTarget);
            newEntry.staticEncoder = staticEncoder;
            newEntry.outEncoder = outEncoder;
            newEntry.taglibEncoder = taglibEncoder;
            newEntry.defaultTaglibEncoder = defaultTaglibEncoder;
            newEntry.expressionEncoder = expressionEncoder;
            return newEntry;
        }
    }

    static class GroovyPageProxyWriterGroup {
        GroovyPageProxyWriter activeWriter;

        void reset() {
            activateWriter(null);
        }

        void activateWriter(GroovyPageProxyWriter newWriter) {
            if (newWriter != activeWriter) {
                flushActive();
                activeWriter = newWriter;
            }
        }

        void flushActive() {
            if (activeWriter != null) {
                activeWriter.flush();
            }
        }
    }

    public class GroovyPageProxyWriter extends GrailsLazyProxyPrintWriter {
        GroovyPageProxyWriterGroup writerGroup;

        GroovyPageProxyWriter(GroovyPageProxyWriterGroup writerGroup, DestinationFactory factory) {
            super(factory);
            this.writerGroup = writerGroup;
        }

        public GroovyPageOutputStack getOutputStack() {
            return GroovyPageOutputStack.this;
        }

        @Override
        public Writer getOut() {
            writerGroup.activateWriter(this);
            return super.getOut();
        }
    }

    private GroovyPageOutputStack(GroovyPageOutputStackAttributes attributes) {
        outWriter = new GroovyPageProxyWriter(writerGroup, new DestinationFactory() {
            public Writer activateDestination() throws IOException {
                StackEntry stackEntry = stack.peek();
                return createEncodingWriter(stackEntry.unwrappedTarget, stackEntry.outEncoder,
                        encodingStateRegistry, GroovyPageConfig.OUT_CODEC_NAME);
            }
        });
        staticWriter = new GroovyPageProxyWriter(writerGroup, new DestinationFactory() {
            public Writer activateDestination() throws IOException {
                StackEntry stackEntry = stack.peek();
                if (stackEntry.staticEncoder == null) {
                    return stackEntry.unwrappedTarget;
                }
                return createEncodingWriter(stackEntry.unwrappedTarget, stackEntry.staticEncoder,
                        encodingStateRegistry, GroovyPageConfig.STATIC_CODEC_NAME);
            }
        });
        expressionWriter = new GroovyPageProxyWriter(writerGroup, new DestinationFactory() {
            public Writer activateDestination() throws IOException {
                StackEntry stackEntry = stack.peek();
                return createEncodingWriter(stackEntry.unwrappedTarget, stackEntry.expressionEncoder,
                        encodingStateRegistry, GroovyPageConfig.EXPRESSION_CODEC_NAME);
            }
        });
        taglibWriter = new GroovyPageProxyWriter(writerGroup, new DestinationFactory() {
            public Writer activateDestination() throws IOException {
                StackEntry stackEntry = stack.peek();
                return createEncodingWriter(stackEntry.unwrappedTarget,
                        stackEntry.taglibEncoder != null ? stackEntry.taglibEncoder
                                : stackEntry.defaultTaglibEncoder,
                        encodingStateRegistry, GroovyPageConfig.TAGLIB_CODEC_NAME);
            }
        });
        this.autoSync = attributes.isAutoSync();
        push(attributes, false);
        if (!autoSync) {
            applyWriterThreadLocals(outWriter);
        }
        this.encodingStateRegistry = attributes.getWebRequest().getEncodingStateRegistry();
    }

    private Writer unwrapTargetWriter(Writer targetWriter) {
        if (targetWriter instanceof GrailsWrappedWriter
                && ((GrailsWrappedWriter) targetWriter).isAllowUnwrappingOut()) {
            return ((GrailsWrappedWriter) targetWriter).unwrap();
        }
        return targetWriter;
    }

    public void push(final Writer newWriter) {
        push(newWriter, false);
    }

    public void push(final Writer newWriter, final boolean checkExisting) {
        GroovyPageOutputStackAttributes.Builder attributesBuilder = new GroovyPageOutputStackAttributes.Builder();
        attributesBuilder.inheritPreviousEncoders(true);
        attributesBuilder.topWriter(newWriter);
        push(attributesBuilder.build(), checkExisting);
    }

    public void push(final GroovyPageOutputStackAttributes attributes) {
        push(attributes, false);
    }

    public void push(final GroovyPageOutputStackAttributes attributes, final boolean checkExisting) {
        writerGroup.reset();

        if (checkExisting)
            checkExistingStack(attributes.getTopWriter());

        StackEntry previousStackEntry = null;
        if (stack.size() > 0) {
            previousStackEntry = stack.peek();
        }

        Writer topWriter = attributes.getTopWriter();
        Writer unwrappedWriter = null;
        if (topWriter != null) {
            if (topWriter instanceof GroovyPageProxyWriter) {
                topWriter = ((GroovyPageProxyWriter) topWriter).getOut();
            }
            unwrappedWriter = unwrapTargetWriter(topWriter);
        } else if (previousStackEntry != null) {
            topWriter = previousStackEntry.originalTarget;
            unwrappedWriter = previousStackEntry.unwrappedTarget;
        } else {
            throw new NullPointerException("attributes.getTopWriter() is null and there is no previous stack item");
        }

        StackEntry stackEntry = new StackEntry(topWriter, unwrappedWriter);
        stackEntry.outEncoder = applyEncoder(attributes.getOutEncoder(),
                previousStackEntry != null ? previousStackEntry.outEncoder : null,
                attributes.isInheritPreviousEncoders());
        stackEntry.staticEncoder = applyEncoder(attributes.getStaticEncoder(),
                previousStackEntry != null ? previousStackEntry.staticEncoder : null,
                attributes.isInheritPreviousEncoders());
        stackEntry.expressionEncoder = applyEncoder(attributes.getExpressionEncoder(),
                previousStackEntry != null ? previousStackEntry.expressionEncoder : null,
                attributes.isInheritPreviousEncoders());
        stackEntry.taglibEncoder = applyEncoder(attributes.getTaglibEncoder(),
                previousStackEntry != null ? previousStackEntry.taglibEncoder : null,
                attributes.isInheritPreviousEncoders());
        stackEntry.defaultTaglibEncoder = applyEncoder(attributes.getDefaultTaglibEncoder(),
                previousStackEntry != null ? previousStackEntry.defaultTaglibEncoder : null,
                attributes.isInheritPreviousEncoders());

        stack.push(stackEntry);

        resetWriters();

        if (autoSync) {
            applyWriterThreadLocals(attributes.getTopWriter());
        }
    }

    private Encoder applyEncoder(Encoder newEncoder, Encoder previousEncoder, boolean allowInheriting) {
        if (newEncoder != null) {
            return newEncoder;
        }
        if (allowInheriting) {
            return previousEncoder;
        }
        return null;
    }

    private void checkExistingStack(final Writer topWriter) {
        if (topWriter != null) {
            for (StackEntry item : stack) {
                if (item.originalTarget == topWriter) {
                    log.warn(
                            "Pushed a writer to stack a second time. Writer type " + topWriter.getClass().getName(),
                            new Exception());
                }
            }
        }
    }

    private void resetWriters() {
        outWriter.setDestinationActivated(false);
        staticWriter.setDestinationActivated(false);
        expressionWriter.setDestinationActivated(false);
        taglibWriter.setDestinationActivated(false);
    }

    private Writer createEncodingWriter(Writer out, Encoder encoder, EncodingStateRegistry encodingStateRegistry,
            String codecWriterName) {
        Writer encodingWriter;
        if (out instanceof EncodedAppenderWriterFactory) {
            encodingWriter = ((EncodedAppenderWriterFactory) out).getWriterForEncoder(encoder,
                    encodingStateRegistry);
        } else {
            encodingWriter = new CodecPrintWriter(out, encoder, encodingStateRegistry);
        }
        return encodingWriter;
    }

    public void pop() {
        pop(autoSync);
    }

    public void pop(boolean forceSync) {
        writerGroup.reset();
        stack.pop();
        resetWriters();
        if (stack.size() > 0) {
            StackEntry stackEntry = stack.peek();
            if (forceSync) {
                applyWriterThreadLocals(stackEntry.originalTarget);
            }
        }
    }

    public GroovyPageProxyWriter getOutWriter() {
        return outWriter;
    }

    public GroovyPageProxyWriter getStaticWriter() {
        return staticWriter;
    }

    public GroovyPageProxyWriter getExpressionWriter() {
        return expressionWriter;
    }

    public GroovyPageProxyWriter getTaglibWriter() {
        return taglibWriter;
    }

    public Encoder getOutEncoder() {
        return stack.size() > 0 ? stack.peek().outEncoder : null;
    }

    public Encoder getStaticEncoder() {
        return stack.size() > 0 ? stack.peek().staticEncoder : null;
    }

    public Encoder getExpressionEncoder() {
        return stack.size() > 0 ? stack.peek().expressionEncoder : null;
    }

    public Encoder getTaglibEncoder() {
        return stack.size() > 0 ? stack.peek().taglibEncoder : null;
    }

    public Encoder getDefaultTaglibEncoder() {
        return stack.size() > 0 ? stack.peek().defaultTaglibEncoder : null;
    }

    public Writer getCurrentOriginalWriter() {
        return stack.peek().originalTarget;
    }

    public void restoreThreadLocalsToOriginals() {
        Writer originalTopWriter = stack.firstElement().originalTarget;
        applyWriterThreadLocals(originalTopWriter);
    }

    private void applyWriterThreadLocals(Writer writer) {
        GrailsWebRequest webRequest = GrailsWebRequest.lookup();
        if (webRequest != null) {
            webRequest.setOut(writer);
        }
    }

    public void flushActiveWriter() {
        writerGroup.flushActive();
    }
}