diff --git a/celements-model/src/test/java/com/xpn/xwiki/internal/XWikiExecutionContextInitializerTest.java b/celements-model/src/test/java/com/xpn/xwiki/internal/XWikiExecutionContextInitializerTest.java new file mode 100644 index 000000000..4597a6c96 --- /dev/null +++ b/celements-model/src/test/java/com/xpn/xwiki/internal/XWikiExecutionContextInitializerTest.java @@ -0,0 +1,39 @@ +package com.xpn.xwiki.internal; + +import static com.celements.execution.XWikiExecutionProp.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.xwiki.context.Execution; +import org.xwiki.context.ExecutionContext; +import org.xwiki.context.ExecutionContextInitializer; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.model.reference.WikiReference; + +import com.xpn.xwiki.doc.XWikiDocument; +import com.celements.common.test.AbstractComponentTest; +import com.xpn.xwiki.user.api.XWikiUser; + +public class XWikiExecutionContextInitializerTest extends AbstractComponentTest { + + @Test + public void test_initializeFromSource() throws Exception { + assertTrue(getBeanFactory().getBeansOfType(ExecutionContextInitializer.class).values().stream() + .anyMatch(XWikiExecutionContextInitializer.class::isInstance)); + ExecutionContext source = getBeanFactory().getBean(Execution.class).getContext(); + XWikiDocument doc = new XWikiDocument(new DocumentReference("wiki", "Space", "Doc")); + XWikiUser user = new XWikiUser("XWiki.User"); + source.set(WIKI, new WikiReference("wiki")); + source.set(DOC, doc); + source.set(XWIKI_USER, user); + + ExecutionContext target = new ExecutionContext(); + getBeanFactory().getBean(XWikiExecutionContextInitializer.class).initialize(target, source); + + assertEquals(new WikiReference("wiki"), target.get(WIKI).orElseThrow()); + assertSame(doc, target.get(DOC).orElseThrow()); + assertSame(user, target.get(XWIKI_USER).orElseThrow()); + assertNotSame(getXContext(), target.get(XWIKI_CONTEXT).orElseThrow()); + } + +} diff --git a/celements-model/src/test/java/org/xwiki/context/internal/DefaultExecutionContextManagerTest.java b/celements-model/src/test/java/org/xwiki/context/internal/DefaultExecutionContextManagerTest.java new file mode 100644 index 000000000..e5893e6f0 --- /dev/null +++ b/celements-model/src/test/java/org/xwiki/context/internal/DefaultExecutionContextManagerTest.java @@ -0,0 +1,50 @@ +package org.xwiki.context.internal; + +import static com.celements.execution.XWikiExecutionProp.*; +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; +import org.xwiki.context.Execution; +import org.xwiki.context.ExecutionContext; + +import com.celements.common.test.AbstractComponentTest; + +public final class DefaultExecutionContextManagerTest extends AbstractComponentTest { + + private DefaultExecutionContextManager contextManager; + private Execution execution; + + @Before + public void prepareTest() throws Exception { + contextManager = getBeanFactory().getBean(DefaultExecutionContextManager.class); + execution = getBeanFactory().getBean(Execution.class); + } + + @Test + public void test_cloneContext() throws Exception { + ExecutionContext context = execution.getContext(); + ExecutionContext clonedContext = contextManager.clone(context); + + assertNotSame(context, clonedContext); + assertTrue(clonedContext.get(XWIKI_CONTEXT).isPresent()); + assertSame(context, execution.getContext()); + } + + @Test + public void test_cloneWithoutCurrentContext() throws Exception { + ExecutionContext originalContext = execution.getContext(); + ExecutionContext context = new ExecutionContext(); + execution.removeContext(); + try { + ExecutionContext clonedContext = contextManager.clone(context); + + assertNotNull(clonedContext); + assertTrue(clonedContext.get(XWIKI_CONTEXT).isPresent()); + assertNull(execution.getContext()); + } finally { + execution.setContext(originalContext); + } + } + +} diff --git a/celements-servlet/pom.xml b/celements-servlet/pom.xml index bf9cc1f71..885699af3 100644 --- a/celements-servlet/pom.xml +++ b/celements-servlet/pom.xml @@ -102,7 +102,6 @@ xwiki-core-shared-tests test - scm:git:git@github.com:celements/celements-base.git diff --git a/celements-servlet/src/main/java/org/xwiki/context/Execution.java b/celements-servlet/src/main/java/org/xwiki/context/Execution.java index 9af332d78..e1620385b 100644 --- a/celements-servlet/src/main/java/org/xwiki/context/Execution.java +++ b/celements-servlet/src/main/java/org/xwiki/context/Execution.java @@ -20,15 +20,12 @@ */ package org.xwiki.context; -import org.xwiki.component.annotation.ComponentRole; - /** * Allows setting/retrieving the {@link ExecutionContext}. * * @version $Id$ * @since 1.5M2 */ -@ComponentRole public interface Execution { ExecutionContext getContext(); diff --git a/celements-servlet/src/main/java/org/xwiki/context/ExecutionContextInitializer.java b/celements-servlet/src/main/java/org/xwiki/context/ExecutionContextInitializer.java index d5a8ee152..d357624be 100644 --- a/celements-servlet/src/main/java/org/xwiki/context/ExecutionContextInitializer.java +++ b/celements-servlet/src/main/java/org/xwiki/context/ExecutionContextInitializer.java @@ -20,11 +20,12 @@ */ package org.xwiki.context; -import org.xwiki.component.annotation.ComponentRole; +import javax.annotation.Nullable; +import javax.validation.constraints.NotNull; -@ComponentRole public interface ExecutionContextInitializer { - void initialize(ExecutionContext context) throws ExecutionContextException; + void initialize(@NotNull ExecutionContext context, @Nullable ExecutionContext source) + throws ExecutionContextException; } diff --git a/celements-servlet/src/main/java/org/xwiki/context/ExecutionContextManager.java b/celements-servlet/src/main/java/org/xwiki/context/ExecutionContextManager.java index d5c9a05aa..c7e58c50f 100644 --- a/celements-servlet/src/main/java/org/xwiki/context/ExecutionContextManager.java +++ b/celements-servlet/src/main/java/org/xwiki/context/ExecutionContextManager.java @@ -20,19 +20,29 @@ */ package org.xwiki.context; -import org.xwiki.component.annotation.ComponentRole; +import javax.validation.constraints.NotNull; -@ComponentRole public interface ExecutionContextManager { - void initialize(ExecutionContext context) throws ExecutionContextException; + /** + * Initializes the given context by asking all registered {@link ExecutionContextInitializer}s to + * initialize their module-specific state. + * + * @param context + * the context to initialize + */ + void initialize(@NotNull ExecutionContext context) throws ExecutionContextException; /** - * Perform deep cloning of Execution Context properties by calling all implementations of - * {@link ExecutionContextCloner}. + * Creates and initializes an independent child context by asking all registered + * {@link ExecutionContextInitializer}s to initialize their module-specific state from the source + * context. * - * @return the cloned Execution Context + * @param context + * the context from which state is inherited + * @return the cloned and initialized context */ - ExecutionContext clone(ExecutionContext context) throws ExecutionContextException; + @NotNull + ExecutionContext clone(@NotNull ExecutionContext context) throws ExecutionContextException; } diff --git a/celements-servlet/src/main/java/org/xwiki/context/internal/DefaultExecution.java b/celements-servlet/src/main/java/org/xwiki/context/internal/DefaultExecution.java index 7bb45dd68..2885e7ddd 100644 --- a/celements-servlet/src/main/java/org/xwiki/context/internal/DefaultExecution.java +++ b/celements-servlet/src/main/java/org/xwiki/context/internal/DefaultExecution.java @@ -20,9 +20,11 @@ */ package org.xwiki.context.internal; +import static com.google.common.base.Preconditions.*; + import java.util.Stack; -import org.xwiki.component.annotation.Component; +import org.springframework.stereotype.Component; import org.xwiki.context.Execution; import org.xwiki.context.ExecutionContext; @@ -41,27 +43,37 @@ public class DefaultExecution implements Execution { private final ThreadLocal> context = new ThreadLocal<>(); + private Stack getOrCreateStack() { + Stack stack = context.get(); + if (stack == null) { + stack = new Stack<>(); + context.set(stack); + } + return stack; + } + @Override - public void pushContext(ExecutionContext context) { - this.context.get().push(context); + public void pushContext(ExecutionContext newContext) { + getOrCreateStack().push(checkNotNull(newContext)); } @Override public void popContext() { - context.get().pop(); + Stack stack = context.get(); + checkState((stack != null) && !stack.isEmpty(), "no execution context to pop"); + stack.pop(); } @Override public ExecutionContext getContext() { Stack stack = context.get(); - return stack == null ? null : stack.peek(); + return (stack == null) || stack.isEmpty() ? null : stack.peek(); } @Override - public void setContext(ExecutionContext context) { - Stack stack = new Stack<>(); - stack.push(context); - this.context.set(stack); + public void setContext(ExecutionContext newContext) { + removeContext(); + pushContext(newContext); } @Override diff --git a/celements-servlet/src/main/java/org/xwiki/context/internal/DefaultExecutionContextManager.java b/celements-servlet/src/main/java/org/xwiki/context/internal/DefaultExecutionContextManager.java index 3d9440ae7..79fdb73b6 100644 --- a/celements-servlet/src/main/java/org/xwiki/context/internal/DefaultExecutionContextManager.java +++ b/celements-servlet/src/main/java/org/xwiki/context/internal/DefaultExecutionContextManager.java @@ -20,11 +20,12 @@ */ package org.xwiki.context.internal; -import java.util.ArrayList; import java.util.List; -import org.xwiki.component.annotation.Component; -import org.xwiki.component.annotation.Requirement; +import javax.inject.Inject; + +import org.springframework.stereotype.Component; +import org.xwiki.context.Execution; import org.xwiki.context.ExecutionContext; import org.xwiki.context.ExecutionContextException; import org.xwiki.context.ExecutionContextInitializer; @@ -33,48 +34,39 @@ @Component public class DefaultExecutionContextManager implements ExecutionContextManager { - @Requirement - private List initializers = new ArrayList<>(); + private final List initializers; + private final Execution execution; + + @Inject + public DefaultExecutionContextManager( + List initializers, + Execution execution) { + this.initializers = List.copyOf(initializers); + this.execution = execution; + } @Override public ExecutionContext clone(ExecutionContext context) throws ExecutionContextException { - // Ideally we would like to do a deep cloning here. However it's just too hard since we don't - // control objects put in the Execution Context and they can be of any type, including Maps - // which are cloneable but only do shallow clones. - // Thus instead we recreate the Execution Context from scratch and reinitialize it by calling - // all the Execution Context Initializers on it. - ExecutionContext clonedContext = new ExecutionContext(); - clonedContext.setProperty("xwikicontext", context.getProperty("xwikicontext")); - initialize(clonedContext); - // Manually clone the Velocity Context too since currently the XWikiVelocityContextInitializer - // is not yet implemented. - // Note that we're using reflection since we don't want to add a dependency on Velocity module - // since that would cause a cyclic dependency. - // TODO: Fix this when XWikiVelocityContextInitializer is implemented - // Note that Velocity doesn't provide a method for cloning a Velocity Context - // (see https://issues.apache.org/jira/browse/VELOCITY-712). Thus we're not cloning the Velocity - // Context which can raise problems if the included page modifies the Velocity Context... - Object velocityContext = context.getProperty("velocityContext"); - if (velocityContext != null) { - try { - clonedContext.setProperty("velocityContext", - velocityContext.getClass().getMethod("clone").invoke(velocityContext)); - } catch (Exception e) { - throw new ExecutionContextException( - "Failed to clone Velocity Context for the new Execution Context", e); - } + var clone = new ExecutionContext(); + execution.pushContext(clone); + try { + initialize(clone, context); + } finally { + execution.popContext(); } - return clonedContext; + return clone; } @Override public void initialize(ExecutionContext context) throws ExecutionContextException { + initialize(context, null); + } + + private void initialize(ExecutionContext context, ExecutionContext source) + throws ExecutionContextException { for (ExecutionContextInitializer initializer : this.initializers) { - initializer.initialize(context); + initializer.initialize(context, source); } } - public void addExecutionContextInitializer(ExecutionContextInitializer initializer) { - this.initializers.add(initializer); - } } diff --git a/celements-servlet/src/main/resources/META-INF/components.txt b/celements-servlet/src/main/resources/META-INF/components.txt index 9eee2c6c3..385ed5568 100644 --- a/celements-servlet/src/main/resources/META-INF/components.txt +++ b/celements-servlet/src/main/resources/META-INF/components.txt @@ -1,5 +1,3 @@ -org.xwiki.context.internal.DefaultExecution -org.xwiki.context.internal.DefaultExecutionContextManager org.xwiki.container.internal.DefaultContainer org.xwiki.container.internal.DefaultRequestInitializerManager org.xwiki.container.internal.DefaultApplicationContextListenerManager diff --git a/celements-servlet/src/test/java/org/xwiki/context/internal/DefaultExecutionContextManagerTest.java b/celements-servlet/src/test/java/org/xwiki/context/internal/DefaultExecutionContextManagerTest.java deleted file mode 100644 index 5e3852f9c..000000000 --- a/celements-servlet/src/test/java/org/xwiki/context/internal/DefaultExecutionContextManagerTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * See the NOTICE file distributed with this work for additional - * information regarding copyright ownership. - * - * This 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.1 of - * the License, or (at your option) any later version. - * - * This software 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 this software; if not, write to the Free - * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA - * 02110-1301 USA, or see the FSF site: http://www.fsf.org. - * - */ -package org.xwiki.context.internal; - -import java.util.Arrays; -import java.util.List; - -import org.xwiki.context.ExecutionContext; - -import junit.framework.TestCase; - -/** - * Unit tests for {@link ExecutionContext}. - * - * @version $Id$ - * @since 1.8RC3 - */ -public class DefaultExecutionContextManagerTest extends TestCase { - - /** - * Verify we have different objects in the Execution Context after the clone. - */ - public void testClone() throws Exception { - ExecutionContext context = new ExecutionContext(); - - DefaultExecutionContextManager contextManager = new DefaultExecutionContextManager(); - contextManager.addExecutionContextInitializer( - context1 -> context1.setProperty("key", Arrays.asList("value"))); - - ExecutionContext clonedContext = contextManager.clone(context); - - assertEquals("value", ((List) clonedContext.getProperty("key")).get(0)); - assertNotSame(context.getProperty("key"), clonedContext.getProperty("key")); - } -} diff --git a/celements-servlet/src/test/java/org/xwiki/context/internal/DefaultExecutionTest.java b/celements-servlet/src/test/java/org/xwiki/context/internal/DefaultExecutionTest.java new file mode 100644 index 000000000..8c21f8eaf --- /dev/null +++ b/celements-servlet/src/test/java/org/xwiki/context/internal/DefaultExecutionTest.java @@ -0,0 +1,48 @@ +package org.xwiki.context.internal; + +import org.xwiki.context.ExecutionContext; + +import junit.framework.TestCase; + +public class DefaultExecutionTest extends TestCase { + + public void testPushAndPopContext() { + DefaultExecution execution = new DefaultExecution(); + ExecutionContext parent = new ExecutionContext(); + ExecutionContext child = new ExecutionContext(); + + assertNull(execution.getContext()); + execution.pushContext(parent); + assertSame(parent, execution.getContext()); + execution.pushContext(child); + assertSame(child, execution.getContext()); + execution.popContext(); + assertSame(parent, execution.getContext()); + execution.popContext(); + assertNull(execution.getContext()); + } + + public void testSetContextReplacesStack() { + DefaultExecution execution = new DefaultExecution(); + execution.pushContext(new ExecutionContext()); + execution.pushContext(new ExecutionContext()); + ExecutionContext replacement = new ExecutionContext(); + + execution.setContext(replacement); + + assertSame(replacement, execution.getContext()); + execution.popContext(); + assertNull(execution.getContext()); + } + + public void testPopWithoutContextFails() { + DefaultExecution execution = new DefaultExecution(); + try { + execution.popContext(); + fail("expected IllegalStateException"); + } catch (IllegalStateException expected) { + assertEquals("no execution context to pop", expected.getMessage()); + } + } + +} diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/XWikiContext.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/XWikiContext.java index c5839f97f..2258bbe14 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/XWikiContext.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/XWikiContext.java @@ -605,4 +605,9 @@ private ExecutionContext getExecutionContext() { } } + @Override + public XWikiContext clone() { + return (XWikiContext) super.clone(); + } + } diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/export/html/HtmlPackager.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/export/html/HtmlPackager.java index 75da1249d..4017afb46 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/export/html/HtmlPackager.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/export/html/HtmlPackager.java @@ -19,6 +19,7 @@ import org.xwiki.context.ExecutionContextManager; import org.xwiki.velocity.VelocityManager; +import com.celements.execution.XWikiExecutionProp; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.XWikiDocument; @@ -205,35 +206,24 @@ private void renderDocuments(ZipOutputStream zos, File tempdir, ExportURLFactory throws XWikiException, IOException { ExecutionContextManager ecim = Utils.getComponent(ExecutionContextManager.class); Execution execution = Utils.getComponent(Execution.class); - - VelocityContext oldVelocityContext = (VelocityContext) context.get("vcontext"); - + ExecutionContext eCtxClone = null; try { - XWikiContext renderContext = (XWikiContext) context.clone(); - renderContext.put("action", "view"); - - ExecutionContext ec = new ExecutionContext(); - - // Bridge with old XWiki Context, required for old code. - ec.setProperty("xwikicontext", renderContext); - - ecim.initialize(ec); - + eCtxClone = ecim.clone(execution.getContext()); // Push a clean new Execution Context since we don't want the main Execution Context to be // used for // rendering the HTML pages to export. It's cleaner to isolate it as we do. Note that the new // Execution Context automatically gets initialized with a new Velocity Context by // the VelocityRequestInitializer class. - execution.pushContext(ec); - - VelocityManager velocityManager = Utils.getComponent(VelocityManager.class); - + execution.pushContext(eCtxClone); + XWikiContext renderContext = eCtxClone.get(XWikiExecutionProp.XWIKI_CONTEXT) + .orElseThrow(IllegalStateException::new); + renderContext.setAction("view"); + renderContext.put("action", "view"); // At this stage we have a clean Velocity Context - VelocityContext vcontext = velocityManager.getVelocityContext(); - + VelocityContext vcontext = Utils.getComponent(VelocityManager.class) + .getVelocityContext(); urlf.init(this.pages, tempdir, renderContext); renderContext.setURLFactory(urlf); - for (String pageName : this.pages) { renderDocument(pageName, zos, renderContext, vcontext); } @@ -244,9 +234,9 @@ private void renderDocuments(ZipOutputStream zos, File tempdir, ExportURLFactory } finally { // We must ensure that the new request we've used is removed so that the current // thread can continue to use its original Execution Context. - execution.popContext(); - - context.put("vcontext", oldVelocityContext); + if (eCtxClone != null) { + execution.popContext(); + } } } diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/internal/XWikiExecutionContextInitializer.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/internal/XWikiExecutionContextInitializer.java index 790f97ba2..9eda2b798 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/internal/XWikiExecutionContextInitializer.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/internal/XWikiExecutionContextInitializer.java @@ -15,6 +15,7 @@ import org.xwiki.context.ExecutionContextInitializer; import com.celements.init.XWikiProvider; +import com.xpn.xwiki.XWikiContext; @Component public class XWikiExecutionContextInitializer implements ExecutionContextInitializer { @@ -22,11 +23,17 @@ public class XWikiExecutionContextInitializer implements ExecutionContextInitial public static final Property NO_AWAIT = new Property<>( "XWikiExecutionContextInitializer.noAwait", Boolean.class); + private final XWikiProvider wikiProvider; + @Inject - private XWikiProvider wikiProvider; + public XWikiExecutionContextInitializer(XWikiProvider wikiProvider) { + this.wikiProvider = wikiProvider; + } @Override - public void initialize(ExecutionContext context) throws ExecutionContextException { + public void initialize(ExecutionContext context, ExecutionContext source) + throws ExecutionContextException { + copyProps(context, source); try { context.computeIfAbsent(XWIKI, rethrow(() -> context.get(NO_AWAIT).orElse(false) ? wikiProvider.get().orElse(null) @@ -36,4 +43,26 @@ public void initialize(ExecutionContext context) throws ExecutionContextExceptio } } + private void copyProps(ExecutionContext context, ExecutionContext source) { + if (source == null) { + return; + } + copyProp(WIKI, source, context); + copyProp(DOC, source, context); + copyProp(XWIKI, source, context); + copyProp(XWIKI_USER, source, context); + copyProp(XWIKI_REQUEST_URI, source, context); + copyProp(XWIKI_REQUEST_ACTION, source, context); + copyProp(XWIKI_REQUEST, source, context); + copyProp(XWIKI_RESPONSE, source, context); + source.get(XWIKI_CONTEXT) + .map(XWikiContext::clone) + .ifPresent(xCtx -> context.set(XWIKI_CONTEXT, xCtx)); + } + + private void copyProp(Property property, ExecutionContext source, + ExecutionContext target) { + source.get(property).ifPresent(value -> target.set(property, value)); + } + } diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/internal/XWikiStubContextInitializer.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/internal/XWikiStubContextInitializer.java index 0809b4e6c..b67263e6f 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/internal/XWikiStubContextInitializer.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/internal/XWikiStubContextInitializer.java @@ -1,7 +1,8 @@ package com.xpn.xwiki.internal; -import org.xwiki.component.annotation.Component; -import org.xwiki.component.annotation.Requirement; +import javax.inject.Inject; + +import org.springframework.stereotype.Component; import org.xwiki.context.ExecutionContext; import org.xwiki.context.ExecutionContextException; import org.xwiki.context.ExecutionContextInitializer; @@ -16,16 +17,21 @@ * @see XWikiStubContextProvider * @since 2.0M3 */ -@Component("XWikiStubContextInitializer") +@Component public class XWikiStubContextInitializer implements ExecutionContextInitializer { - @Requirement - private XWikiStubContextProvider stubContextProvider; + private final XWikiStubContextProvider provider; + + @Inject + public XWikiStubContextInitializer(XWikiStubContextProvider provider) { + this.provider = provider; + } @Override - public void initialize(ExecutionContext context) throws ExecutionContextException { + public void initialize(ExecutionContext context, ExecutionContext source) + throws ExecutionContextException { context.computeIfAbsent(XWikiExecutionProp.XWIKI_CONTEXT, - () -> stubContextProvider.createStubContext(context)); + () -> provider.createStubContext(context)); } } diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/render/DefaultVelocityManager.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/render/DefaultVelocityManager.java index cb1e93420..a806cbd71 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/render/DefaultVelocityManager.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/render/DefaultVelocityManager.java @@ -20,6 +20,8 @@ */ package com.xpn.xwiki.render; +import static org.xwiki.velocity.VelocityExecutionProp.*; + import java.util.HashMap; import java.util.Map; import java.util.Properties; @@ -35,7 +37,6 @@ import org.xwiki.velocity.VelocityFactory; import org.xwiki.velocity.VelocityManager; import org.xwiki.velocity.XWikiVelocityException; -import org.xwiki.velocity.internal.VelocityExecutionContextInitializer; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.api.Context; @@ -69,8 +70,7 @@ public VelocityContext getVelocityContext() { // The Velocity Context is set in VelocityRequestInterceptor, when the XWiki Request is // initialized so we are // guaranteed it is defined when this method is called. - VelocityContext vcontext = (VelocityContext) this.execution.getContext().getProperty( - VelocityExecutionContextInitializer.VELOCITY_CONTEXT_ID); + VelocityContext vcontext = this.execution.getContext().get(VELOCITY_CONTEXT).orElseThrow(); // Bridge. To be removed later. if (vcontext.get("util") == null) { diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateStore.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateStore.java index 54e096b96..a94208b0a 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateStore.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/store/XWikiHibernateStore.java @@ -1003,17 +1003,17 @@ private void saveLinks10(XWikiDocument doc, XWikiContext context, Session sessio // renderer uses context.getDoc().getSpace() to find out the space name if no // space is specified in the link. A better implementation would be to pass // explicitely the current space to the render() method. - ExecutionContext econtext = new ExecutionContext(); Execution execution = Utils.getComponent(Execution.class); List links; try { // Create new clean context to avoid wiki manager plugin requests in same session - XWikiContext renderContext = (XWikiContext) context.clone(); - renderContext.setDoc(doc); - econtext.set(XWIKI_CONTEXT, renderContext); - Utils.getComponent(ExecutionContextManager.class).initialize(econtext); + ExecutionContext econtext = Utils.getComponent(ExecutionContextManager.class) + .clone(execution.getContext()); execution.pushContext(econtext); try { + econtext.set(DOC, doc); + XWikiContext renderContext = econtext.get(XWIKI_CONTEXT) + .orElseThrow(IllegalStateException::new); XWikiRenderer renderer = renderContext.getWiki().getRenderingEngine().getRenderer("wiki"); renderer.render(doc.getContent(), doc, doc, renderContext); links = (List) renderContext.get("links"); diff --git a/celements-xwiki-core/src/main/java/com/xpn/xwiki/web/XWikiMessageTool.java b/celements-xwiki-core/src/main/java/com/xpn/xwiki/web/XWikiMessageTool.java index 57176bbfc..fa6a599ba 100644 --- a/celements-xwiki-core/src/main/java/com/xpn/xwiki/web/XWikiMessageTool.java +++ b/celements-xwiki-core/src/main/java/com/xpn/xwiki/web/XWikiMessageTool.java @@ -102,6 +102,9 @@ public class XWikiMessageTool { */ protected XWikiContext context; + /** Optional language used instead of the current context language. */ + private final String language; + /** * Cache properties loaded from the document bundles for maximum efficiency. The map is of type * (Long, Properties) @@ -137,8 +140,18 @@ public class XWikiMessageTool { * documents */ public XWikiMessageTool(ResourceBundle bundle, XWikiContext context) { + this(bundle, context, null); + } + + /** + * @param language + * the language used to resolve translated document bundles, or {@code null} to use the + * current context language + */ + public XWikiMessageTool(ResourceBundle bundle, XWikiContext context, String language) { this.bundle = bundle; this.context = context; + this.language = language; } /** @@ -264,7 +277,7 @@ public XWikiDocument getDocumentBundle(String documentName) { try { // First, looks for a document suffixed by the language docBundle = this.context.getWiki().getDocument(documentName, this.context); - docBundle = docBundle.getTranslatedDocument(this.context); + docBundle = getTranslatedDocument(docBundle); } catch (XWikiException e) { // Error while loading the document. // TODO: A runtime exception should be thrown that will bubble up till the @@ -297,7 +310,7 @@ public List getDocumentBundles(String documentName, String defaul try { // First, looks for a document suffixed by the language XWikiDocument docBundle = this.context.getWiki().getDocument(documentName, this.context); - XWikiDocument tdocBundle = docBundle.getTranslatedDocument(this.context); + XWikiDocument tdocBundle = getTranslatedDocument(docBundle); list.add(tdocBundle); if (!tdocBundle.getRealLanguage().equals(defaultLanguage)) { XWikiDocument defdocBundle = docBundle.getTranslatedDocument(defaultLanguage, @@ -318,6 +331,12 @@ public List getDocumentBundles(String documentName, String defaul return list; } + private XWikiDocument getTranslatedDocument(XWikiDocument document) throws XWikiException { + return (language != null) + ? document.getTranslatedDocument(language, context) + : document.getTranslatedDocument(context); + } + /** * @param docBundle * the document bundle. diff --git a/celements-xwiki-core/src/main/resources/META-INF/components.txt b/celements-xwiki-core/src/main/resources/META-INF/components.txt index 872a2364e..35a1086e2 100644 --- a/celements-xwiki-core/src/main/resources/META-INF/components.txt +++ b/celements-xwiki-core/src/main/resources/META-INF/components.txt @@ -14,7 +14,6 @@ com.xpn.xwiki.store.VoidAttachmentVersioningStore com.xpn.xwiki.store.XWikiHibernateRecycleBinStore com.xpn.xwiki.store.hibernate.HibernateAttachmentRecycleBinStore com.xpn.xwiki.internal.DefaultCoreConfiguration -com.xpn.xwiki.internal.XWikiStubContextInitializer com.xpn.xwiki.internal.model.reference.CompactStringEntityReferenceSerializer com.xpn.xwiki.internal.model.reference.CompactWikiStringEntityReferenceSerializer com.xpn.xwiki.internal.model.reference.CurrentEntityReferenceValueProvider diff --git a/celements-xwiki-velocity/pom.xml b/celements-xwiki-velocity/pom.xml index 73c792c35..ce2a68925 100644 --- a/celements-xwiki-velocity/pom.xml +++ b/celements-xwiki-velocity/pom.xml @@ -11,6 +11,11 @@ celements-xwiki-velocity 7.1-SNAPSHOT + + com.celements + celements-commons + provided + com.celements celements-component @@ -147,6 +152,10 @@ jmock-cglib test + + org.springframework + spring-context + diff --git a/celements-xwiki-velocity/src/main/java/org/xwiki/velocity/VelocityExecutionProp.java b/celements-xwiki-velocity/src/main/java/org/xwiki/velocity/VelocityExecutionProp.java new file mode 100644 index 000000000..4d51c4793 --- /dev/null +++ b/celements-xwiki-velocity/src/main/java/org/xwiki/velocity/VelocityExecutionProp.java @@ -0,0 +1,13 @@ +package org.xwiki.velocity; + +import org.apache.velocity.VelocityContext; +import org.xwiki.context.ExecutionContext.Property; + +public final class VelocityExecutionProp { + + public static final Property VELOCITY_CONTEXT = new Property<>( + "velocityContext", VelocityContext.class); + + private VelocityExecutionProp() {} + +} diff --git a/celements-xwiki-velocity/src/main/java/org/xwiki/velocity/internal/VelocityExecutionContextInitializer.java b/celements-xwiki-velocity/src/main/java/org/xwiki/velocity/internal/VelocityExecutionContextInitializer.java index 0eb674b50..83423a9cb 100644 --- a/celements-xwiki-velocity/src/main/java/org/xwiki/velocity/internal/VelocityExecutionContextInitializer.java +++ b/celements-xwiki-velocity/src/main/java/org/xwiki/velocity/internal/VelocityExecutionContextInitializer.java @@ -20,50 +20,44 @@ */ package org.xwiki.velocity.internal; +import static com.celements.common.lambda.LambdaExceptionUtil.*; +import static org.xwiki.velocity.VelocityExecutionProp.*; + +import java.util.Optional; + +import javax.inject.Inject; + import org.apache.velocity.VelocityContext; -import org.xwiki.component.annotation.Component; -import org.xwiki.component.annotation.Requirement; +import org.springframework.stereotype.Component; import org.xwiki.context.ExecutionContext; -import org.xwiki.context.ExecutionContextInitializer; import org.xwiki.context.ExecutionContextException; +import org.xwiki.context.ExecutionContextInitializer; import org.xwiki.velocity.VelocityContextFactory; import org.xwiki.velocity.XWikiVelocityException; /** * Allow registering the Velocity Context in the Execution Context object since it's shared during - * the whole execution - * of the current request. - * - * @see org.xwiki.context.ExecutionContextInitializer - * @since 1.5M1 - * @version $Id$ + * the whole execution of the current request. */ -@Component("velocity") +@Component public class VelocityExecutionContextInitializer implements ExecutionContextInitializer { - /** - * The id under which the Velocity Context is stored in the Execution Context. - */ - public static final String VELOCITY_CONTEXT_ID = "velocityContext"; + private final VelocityContextFactory factory; - /** - * The Velocity context factory component used for creating the Velocity Context (injected - * automatically by the - * Component subsystem). - */ - @Requirement - private VelocityContextFactory velocityContextFactory; + @Inject + public VelocityExecutionContextInitializer(VelocityContextFactory factory) { + this.factory = factory; + } - /** - * {@inheritDoc} - * - * @see org.xwiki.context.ExecutionContextInitializer#initialize(org.xwiki.context.ExecutionContext) - */ - public void initialize(ExecutionContext executionContext) throws ExecutionContextException { + @Override + public void initialize(ExecutionContext context, ExecutionContext source) + throws ExecutionContextException { try { - VelocityContext context = this.velocityContextFactory.createContext(); - executionContext.setProperty(VelocityExecutionContextInitializer.VELOCITY_CONTEXT_ID, - context); + var vCtx = Optional.ofNullable(source) + .flatMap(eCtx -> eCtx.get(VELOCITY_CONTEXT)) + .map(VelocityContext::new) + .orElseGet(rethrowSupplier(factory::createContext)); + context.set(VELOCITY_CONTEXT, vCtx); } catch (XWikiVelocityException e) { throw new ExecutionContextException("Failed to initialize Velocity Context", e); } diff --git a/celements-xwiki-velocity/src/main/resources/META-INF/components.txt b/celements-xwiki-velocity/src/main/resources/META-INF/components.txt index 01fd7bac7..4c7f787a5 100644 --- a/celements-xwiki-velocity/src/main/resources/META-INF/components.txt +++ b/celements-xwiki-velocity/src/main/resources/META-INF/components.txt @@ -1,6 +1,5 @@ org.xwiki.velocity.internal.DefaultVelocityFactory -org.xwiki.velocity.internal.VelocityExecutionContextInitializer org.xwiki.velocity.internal.DefaultVelocityEngine org.xwiki.velocity.internal.DefaultVelocityContextFactory org.xwiki.velocity.internal.DefaultVelocityConfiguration -org.xwiki.velocity.internal.ServicesVelocityContextInitializer \ No newline at end of file +org.xwiki.velocity.internal.ServicesVelocityContextInitializer diff --git a/celements-xwiki-velocity/src/test/java/org/xwiki/velocity/internal/VelocityExecutionContextInitializerTest.java b/celements-xwiki-velocity/src/test/java/org/xwiki/velocity/internal/VelocityExecutionContextInitializerTest.java new file mode 100644 index 000000000..b73c96fc0 --- /dev/null +++ b/celements-xwiki-velocity/src/test/java/org/xwiki/velocity/internal/VelocityExecutionContextInitializerTest.java @@ -0,0 +1,68 @@ +package org.xwiki.velocity.internal; + +import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; +import static org.xwiki.velocity.VelocityExecutionProp.*; + +import org.apache.velocity.VelocityContext; +import org.junit.Test; +import org.xwiki.context.ExecutionContext; +import org.xwiki.velocity.VelocityContextFactory; + +public class VelocityExecutionContextInitializerTest { + + @Test + public void test_initializeFreshContext() throws Exception { + VelocityContextFactory factory = createMock(VelocityContextFactory.class); + VelocityContext freshContext = new VelocityContext(); + expect(factory.createContext()).andReturn(freshContext); + replay(factory); + + ExecutionContext context = new ExecutionContext(); + new VelocityExecutionContextInitializer(factory).initialize(context, null); + + assertSame(freshContext, context.get(VELOCITY_CONTEXT).orElseThrow()); + verify(factory); + } + + @Test + public void test_initializeChildContextInheritsSourceVariables() throws Exception { + VelocityContext sourceVelocityContext = new VelocityContext(); + sourceVelocityContext.put("doc", "sourceDoc"); + sourceVelocityContext.put("services", "configuredServices"); + ExecutionContext source = new ExecutionContext(); + source.set(VELOCITY_CONTEXT, sourceVelocityContext); + VelocityContextFactory factory = createMock(VelocityContextFactory.class); + replay(factory); + + ExecutionContext context = new ExecutionContext(); + new VelocityExecutionContextInitializer(factory).initialize(context, source); + + VelocityContext child = context.get(VELOCITY_CONTEXT).orElseThrow(); + assertNotSame(sourceVelocityContext, child); + assertEquals("sourceDoc", child.get("doc")); + assertEquals("configuredServices", child.get("services")); + verify(factory); + } + + @Test + public void test_initializeChildContextIsolatesWrites() throws Exception { + VelocityContext sourceVelocityContext = new VelocityContext(); + sourceVelocityContext.put("doc", "sourceDoc"); + ExecutionContext source = new ExecutionContext(); + source.set(VELOCITY_CONTEXT, sourceVelocityContext); + VelocityContextFactory factory = createMock(VelocityContextFactory.class); + replay(factory); + + ExecutionContext context = new ExecutionContext(); + new VelocityExecutionContextInitializer(factory).initialize(context, source); + + VelocityContext child = context.get(VELOCITY_CONTEXT).orElseThrow(); + child.put("doc", "childDoc"); + child.put("childOnly", true); + assertEquals("sourceDoc", sourceVelocityContext.get("doc")); + assertNull(sourceVelocityContext.get("childOnly")); + verify(factory); + } + +}