/**
* This file Copyright (c) 2011-2012 Magnolia International
* Ltd. (http://www.magnolia-cms.com). All rights reserved.
*
*
* This file is dual-licensed under both the Magnolia
* Network Agreement and the GNU General Public License.
* You may elect to use one or the other of these licenses.
*
* This file is distributed in the hope that it will be
* useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
* Redistribution, except as permitted by whichever of the GPL
* or MNA you select, is prohibited.
*
* 1. For the GPL license (GPL), you can redistribute and/or
* modify this file under the terms of the GNU General
* Public License, Version 3, as published by the Free Software
* Foundation. You should have received a copy of the GNU
* General Public License, Version 3 along with this program;
* if not, write to the Free Software Foundation, Inc., 51
* Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 2. For the Magnolia Network Agreement (MNA), this file
* and the accompanying materials are made available under the
* terms of the MNA which accompanies this distribution, and
* is available at http://www.magnolia-cms.com/mna.html
*
* Any modifications to this file must keep this entire header
* intact.
*
*/
package info.magnolia.rendering.context;
import info.magnolia.cms.core.AggregationState;
import info.magnolia.cms.core.Content;
import info.magnolia.cms.util.ContentUtil;
import info.magnolia.objectfactory.annotation.LocalScoped;
import info.magnolia.rendering.engine.OutputProvider;
import info.magnolia.rendering.engine.RenderException;
import info.magnolia.rendering.engine.RenderExceptionHandler;
import info.magnolia.rendering.template.RenderableDefinition;
import info.magnolia.rendering.util.AppendableWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.util.EmptyStackException;
import java.util.Stack;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.jcr.Node;
/**
* RenderingContext implementation that uses AggregationState.
*
* @version $Id$
*/
@LocalScoped
public class AggregationStateBasedRenderingContext implements RenderingContext {
// TODO dlipp: add reasonable javadoc! Uses and updates the {@link AggregationState}.
// FIXME we should not use the AggregationState anymore
private static class StackState {
RenderableDefinition renderableDefinition;
OutputProvider outputProvider;
Content legacyContent;
private StackState(RenderableDefinition renderableDefinition, OutputProvider outputProvider, Content legacyContent) {
this.renderableDefinition = renderableDefinition;
this.outputProvider = outputProvider;
this.legacyContent = legacyContent;
}
}
private final AggregationState aggregationState;
private final Stack<StackState> stack = new Stack<StackState>();
private RenderableDefinition currentRenderableDefinition;
private OutputProvider currentOutputProvider;
private RenderExceptionHandler exceptionHandler;
/**
* We keep the current state in local variables and start using the stack only for the second push operation. This
* variable is 0 before the first push, 1 when we have local variables set, and greater than 1 when we have things
* on stack.
*/
private int depth = 0;
@Inject
public AggregationStateBasedRenderingContext(Provider<AggregationState> aggregationStateProvider, RenderExceptionHandler exceptionHandler) {
this(aggregationStateProvider.get(), exceptionHandler);
}
public AggregationStateBasedRenderingContext(AggregationState aggregationState, RenderExceptionHandler exceptionHandler) {
this.aggregationState = aggregationState;
this.exceptionHandler = exceptionHandler;
}
@Override
public Node getMainContent() {
// there is still a possibility of call to this method before push!
Content mainContent = aggregationState.getMainContent();
return mainContent != null ? mainContent.getJCRNode() : null;
}
@Override
public Node getCurrentContent() {
Content currentContent = aggregationState.getCurrentContent();
return currentContent != null ? currentContent.getJCRNode() : null;
}
@Override
public RenderableDefinition getRenderableDefinition() {
return currentRenderableDefinition;
}
@Override
public void push(Node content, RenderableDefinition renderableDefinition) {
push(content, renderableDefinition, null);
}
@Override
public void push(Node content, RenderableDefinition renderableDefinition, OutputProvider outputProvider) {
// Creating the Content object can fail with an exception, by doing it before anything else we don't risk ending
// up having inconsistent state due to a partially completed push.
Content legacyContent = ContentUtil.asContent(content);
if (aggregationState.getMainContent() == null) {
aggregationState.setMainContent(legacyContent);
}
if (depth > 0) {
stack.push(new StackState(currentRenderableDefinition, currentOutputProvider, aggregationState.getCurrentContent()));
}
aggregationState.setCurrentContent(legacyContent);
currentRenderableDefinition = renderableDefinition;
currentOutputProvider = outputProvider != null ? outputProvider : currentOutputProvider;
depth++;
}
@Override
public void pop() {
if (depth == 0) {
throw new EmptyStackException();
} else if (depth == 1) {
aggregationState.setCurrentContent(null);
currentRenderableDefinition = null;
currentOutputProvider = null;
} else {
StackState state = stack.pop();
aggregationState.setCurrentContent(state.legacyContent);
currentRenderableDefinition = state.renderableDefinition;
currentOutputProvider = state.outputProvider;
}
depth--;
// Note that we do not restore main content
}
@Override
public OutputProvider getOutputProvider() {
return currentOutputProvider;
}
@Override
public AppendableWriter getAppendable() throws IOException {
return new AppendableWriter(this.currentOutputProvider.getAppendable());
}
@Override
public OutputStream getOutputStream() throws IOException {
return this.currentOutputProvider.getOutputStream();
}
@Override
public void handleException(RenderException renderException) {
exceptionHandler.handleException(renderException, this);
}
}