Repository: wicket
Updated Branches:
  refs/heads/master 693dad38a -> e7597f03f


easier rendering of components, closes #249

setup ThreadLocal independently from filter or tester


Project: http://git-wip-us.apache.org/repos/asf/wicket/repo
Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/a148e088
Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/a148e088
Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/a148e088

Branch: refs/heads/master
Commit: a148e088f13a4ec1cebfc938e24d6215e19576c8
Parents: 693dad3
Author: Sven Meier <[email protected]>
Authored: Thu Dec 7 09:24:12 2017 +0100
Committer: Sven Meier <[email protected]>
Committed: Thu Dec 7 20:40:36 2017 +0100

----------------------------------------------------------------------
 .../core/util/string/ComponentRenderer.java     | 346 ++++++++++++++++++-
 .../ComponentRendererInstanceTest.java          |  51 +++
 2 files changed, 379 insertions(+), 18 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/wicket/blob/a148e088/wicket-core/src/main/java/org/apache/wicket/core/util/string/ComponentRenderer.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/main/java/org/apache/wicket/core/util/string/ComponentRenderer.java
 
b/wicket-core/src/main/java/org/apache/wicket/core/util/string/ComponentRenderer.java
index 0cf63bf..d583057 100644
--- 
a/wicket-core/src/main/java/org/apache/wicket/core/util/string/ComponentRenderer.java
+++ 
b/wicket-core/src/main/java/org/apache/wicket/core/util/string/ComponentRenderer.java
@@ -16,18 +16,34 @@
  */
 package org.apache.wicket.core.util.string;
 
+import java.io.Serializable;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Supplier;
+
 import org.apache.wicket.Application;
 import org.apache.wicket.Component;
 import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.Page;
+import org.apache.wicket.RuntimeConfigurationType;
+import org.apache.wicket.Session;
 import org.apache.wicket.ThreadContext;
 import org.apache.wicket.core.request.handler.PageProvider;
 import org.apache.wicket.markup.IMarkupCacheKeyProvider;
 import org.apache.wicket.markup.IMarkupResourceStreamProvider;
 import org.apache.wicket.markup.MarkupNotFoundException;
 import org.apache.wicket.markup.html.WebPage;
+import org.apache.wicket.mock.MockApplication;
+import org.apache.wicket.mock.MockWebRequest;
 import org.apache.wicket.protocol.http.BufferedWebResponse;
+import org.apache.wicket.protocol.http.WebApplication;
+import org.apache.wicket.protocol.http.mock.MockServletContext;
+import org.apache.wicket.request.Request;
 import org.apache.wicket.request.Response;
+import org.apache.wicket.request.Url;
 import org.apache.wicket.request.cycle.RequestCycle;
+import org.apache.wicket.serialize.ISerializer;
+import org.apache.wicket.session.ISessionStore;
 import org.apache.wicket.util.resource.IResourceStream;
 import org.apache.wicket.util.resource.StringResourceStream;
 import org.slf4j.Logger;
@@ -35,20 +51,303 @@ import org.slf4j.LoggerFactory;
 
 /**
  * A helper class for rendering components and pages.
- *
- * <p><strong>Note</strong>: {@link #renderComponent(Component)} does 
<strong>not</strong>
- * support rendering {@link org.apache.wicket.markup.html.panel.Fragment} 
instances!</p>
+ * <p>
+ * With the static methods of this class components and pages can be rendered 
on a thread already
+ * processing an {@link Application}.
+ * <p>
+ * If you want to render independently from any web request processing (e.g. 
generating an email
+ * body on a worker thread), you can create an instance of this class.<br/>
+ * You may use an existing application, create a fresh one or just use the 
automatically created
+ * mocked application with sensible defaults.
+ * <p>
+ * Note: For performance reasons instances can and should be reused, be sure 
to call {@link #destroy()} when
+ * they are no longer needed.
  */
 public class ComponentRenderer
 {
        private static final Logger LOGGER = 
LoggerFactory.getLogger(ComponentRenderer.class);
 
+       private Application application;
+
+       /**
+        * A renderer using a default mocked application, which
+        * <ul>
+        * <li>never shares anything in a session</li>
+        * <li>never serializes anything</li>
+        * </ul>
+        */
+       public ComponentRenderer()
+       {
+               this(new MockApplication()
+               {
+                       @Override
+                       public RuntimeConfigurationType getConfigurationType()
+                       {
+                               return RuntimeConfigurationType.DEPLOYMENT;
+                       }
+
+                       @Override
+                       protected void init()
+                       {
+                               super.init();
+
+                               setSessionStoreProvider(() -> new 
NeverSessionStore());
+                               getFrameworkSettings().setSerializer(new 
NeverSerializer());
+                       }
+               });
+       }
+
        /**
-        * Collects the html generated by the rendering of a page.
+        * A renderer using the given application.
+        * <p>
+        * If the application was not yet initialized - e.g. it is not reused 
from an already running
+        * web container - it will be initialized.
+        * 
+        * @param application the application to render components in
+        * 
+        * @see Application#initApplication()
+        */
+       public ComponentRenderer(Application application)
+       {
+               this.application = application;
+
+               if (application.getName() == null)
+               {
+                       // not yet initialized
+                       inThreadContext(this::initApplication);
+               }
+       }
+
+       private void initApplication()
+       {
+               if (application instanceof WebApplication) {
+                       WebApplication webApplication = 
(WebApplication)application;
+                       
+                       // WebApplication requires a servlet context
+                       webApplication.setServletContext(new 
MockServletContext(application, null));
+               }
+               
+               application.setName("ComponentRenderer[" + 
System.identityHashCode(ComponentRenderer.this) + "]");
+               application.initApplication();
+       }
+
+       /**
+        * Destroy this renderer.
+        */
+       public void destroy()
+       {
+               inThreadContext(() -> {
+                       application.internalDestroy();
+                       application = null;
+               });
+       }
+
+       /**
+        * 
+        * Collects the Html generated by rendering a component.
+        * 
+        * @param component
+        *            supplier of the component
+        * @return html rendered by the panel
+        */
+       public CharSequence renderComponent(final Supplier<Component> component)
+       {
+               return renderPage(() -> new RenderPage(component.get()));
+       }
+
+       /**
+        * Collects the html generated by rendering a page.
+        *
+        * @param page
+        *            supplier of the page
+        * @return the html rendered by the panel
+        */
+       public CharSequence renderPage(final Supplier<? extends Page> page)
+       {
+               return inThreadContext(() -> {
+                       Request request = newRequest();
+
+                       BufferedWebResponse response = new 
BufferedWebResponse(null);
+
+                       RequestCycle cycle = 
application.createRequestCycle(request, response);
+
+                       ThreadContext.setRequestCycle(cycle);
+
+                       page.get().renderPage();
+
+                       return response.getText();
+               });
+       }
+
+       /**
+        * Run the given runnable inside a bound {@link ThreadContext}.
+        * 
+        * @param runnable
+        *            runnable
+        */
+       private void inThreadContext(Runnable runnable)
+       {
+               inThreadContext(() -> {
+                       runnable.run();
+                       return null;
+               });
+       }
+
+       /**
+        * Get the result from the given supplier inside a bound {@link 
ThreadContext}.
+        * 
+        * @param supplier
+        *            supplier
+        * @return result of {@link Supplier#get()}
+        */
+       private <T> T inThreadContext(Supplier<T> supplier)
+       {
+               ThreadContext oldContext = ThreadContext.detach();
+
+               try
+               {
+                       ThreadContext.setApplication(application);
+
+                       return supplier.get();
+               }
+               finally
+               {
+
+                       ThreadContext.restore(oldContext);
+               }
+       }
+
+       /**
+        * Create a new request, by default a {@link MockWebRequest}.
+        */
+       protected Request newRequest()
+       {
+               return new MockWebRequest(Url.parse("/"));
+       }
+
+       /**
+        * Never serialize.
+        */
+       private static final class NeverSerializer implements ISerializer
+       {
+               @Override
+               public byte[] serialize(Object object)
+               {
+                       return null;
+               }
+
+               @Override
+               public Object deserialize(byte[] data)
+               {
+                       return null;
+               }
+       }
+
+       /**
+        * Never share anything.
+        */
+       private static class NeverSessionStore implements ISessionStore
+       {
+
+               @Override
+               public Serializable getAttribute(Request request, String name)
+               {
+                       return null;
+               }
+
+               @Override
+               public List<String> getAttributeNames(Request request)
+               {
+                       return null;
+               }
+
+               @Override
+               public void setAttribute(Request request, String name, 
Serializable value)
+               {
+               }
+
+               @Override
+               public void removeAttribute(Request request, String name)
+               {
+               }
+
+               @Override
+               public void invalidate(Request request)
+               {
+               }
+
+               @Override
+               public String getSessionId(Request request, boolean create)
+               {
+                       return null;
+               }
+
+               @Override
+               public Session lookup(Request request)
+               {
+                       return null;
+               }
+
+               @Override
+               public void bind(Request request, Session newSession)
+               {
+               }
+
+               @Override
+               public void flushSession(Request request, Session session)
+               {
+               }
+
+               @Override
+               public void destroy()
+               {
+               }
+
+               @Override
+               public void registerUnboundListener(UnboundListener listener)
+               {
+               }
+
+               @Override
+               public void unregisterUnboundListener(UnboundListener listener)
+               {
+               }
+
+               @Override
+               public Set<UnboundListener> getUnboundListener()
+               {
+                       return null;
+               }
+
+               @Override
+               public void registerBindListener(BindListener listener)
+               {
+               }
+
+               @Override
+               public void unregisterBindListener(BindListener listener)
+               {
+               }
+
+
+               @Override
+
+               public Set<BindListener> getBindListeners()
+               {
+                       return null;
+               }
+       }
+
+       /**
+        * Collects the Html generated by the rendering a page.
+        * <p>
+        * Important note: Must be called on a thread bound to an application's 
{@link ThreadContext}!
         *
         * @param pageProvider
         *            the provider of the page class/instance and its parameters
         * @return the html rendered by a page
+        * 
+        * @see ThreadContext
         */
        public static CharSequence renderPage(final PageProvider pageProvider)
        {
@@ -58,7 +357,8 @@ public class ComponentRenderer
 
                BufferedWebResponse tempResponse = new 
BufferedWebResponse(null);
 
-               RequestCycle tempRequestCycle = 
application.createRequestCycle(originalRequestCycle.getRequest(), tempResponse);
+               RequestCycle tempRequestCycle = application
+                       .createRequestCycle(originalRequestCycle.getRequest(), 
tempResponse);
 
                try
                {
@@ -74,17 +374,23 @@ public class ComponentRenderer
        }
 
        /**
-        * Collects the html generated by the rendering of a component.
-        *
+        * Collects the Html generated by rendering a component.
         * <p>
-        * NOTE: this method is meant to render fresh component instances that 
are disposed after the
-        * html has been generate. To avoid unwanted side effects do not use it 
with components that 
-        * are from an existing hierarchy.
-        * </p>
+        * Important notes:
+        * <ul>
+        * <li>this method is meant to render fresh component instances that 
are disposed after the html
+        * has been generate. To avoid unwanted side effects do not use it with 
components that are from
+        * an existing hierarchy.</li>
+        * <li>does <strong>not</strong> support rendering
+        * {@link org.apache.wicket.markup.html.panel.Fragment} instances</li>
+        * <li>must be called on a thread bound to an application's {@link 
ThreadContext}!</li>
+        * </ul>
         *
         * @param component
         *            the component to render.
         * @return the html rendered by the component
+        * 
+        * @see ThreadContext
         */
        public static CharSequence renderComponent(final Component component)
        {
@@ -97,10 +403,9 @@ public class ComponentRenderer
 
                if (oldParent != null && LOGGER.isWarnEnabled())
                {
-                       LOGGER.warn("Component '{}' with a parent '{}' is 
passed for standalone rendering. " +
-                                       "It is recommended to render only 
orphan components because they are not cleaned up/detached" +
-                                       " after the rendering.",
-                                       component, oldParent);
+                       LOGGER.warn("Component '{}' with a parent '{}' is 
passed for standalone rendering. "
+                               + "It is recommended to render only orphan 
components because they are not cleaned up/detached"
+                               + " after the rendering.", component, 
oldParent);
                }
 
                try
@@ -129,7 +434,10 @@ public class ComponentRenderer
        /**
         * A page used as a parent for the component based rendering.
         */
-       private static class RenderPage extends WebPage implements 
IMarkupResourceStreamProvider, IMarkupCacheKeyProvider
+       private static class RenderPage extends WebPage
+               implements
+                       IMarkupResourceStreamProvider,
+                       IMarkupCacheKeyProvider
        {
                /**
                 * Markup to use when the component to render is not already 
added to a MarkupContainer
@@ -150,7 +458,8 @@ public class ComponentRenderer
                        try
                        {
                                componentMarkup = 
component.getMarkup().toString(true);
-                       } catch (MarkupNotFoundException mnfx)
+                       }
+                       catch (MarkupNotFoundException mnfx)
                        {
                                componentMarkup = String.format(DEFAULT_MARKUP, 
component.getId());
                        }
@@ -159,7 +468,8 @@ public class ComponentRenderer
                }
 
                @Override
-               public IResourceStream getMarkupResourceStream(MarkupContainer 
container, Class<?> containerClass)
+               public IResourceStream getMarkupResourceStream(MarkupContainer 
container,
+                       Class<?> containerClass)
                {
                        return new StringResourceStream(markup);
                }

http://git-wip-us.apache.org/repos/asf/wicket/blob/a148e088/wicket-core/src/test/java/org/apache/wicket/core/util/string/componentrenderer/ComponentRendererInstanceTest.java
----------------------------------------------------------------------
diff --git 
a/wicket-core/src/test/java/org/apache/wicket/core/util/string/componentrenderer/ComponentRendererInstanceTest.java
 
b/wicket-core/src/test/java/org/apache/wicket/core/util/string/componentrenderer/ComponentRendererInstanceTest.java
new file mode 100644
index 0000000..fe5aafb
--- /dev/null
+++ 
b/wicket-core/src/test/java/org/apache/wicket/core/util/string/componentrenderer/ComponentRendererInstanceTest.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.wicket.core.util.string.componentrenderer;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.wicket.core.util.string.ComponentRenderer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for {@link ComponentRenderer}
+ */
+public class ComponentRendererInstanceTest
+{
+       private ComponentRenderer renderer;
+
+       @Before
+       public void setup() {
+               renderer = new ComponentRenderer();
+       }
+       
+       @After
+       public void destroy() {
+               renderer.destroy();
+       }
+       
+       @Test
+       public void render()
+       {
+               CharSequence html = renderer.renderComponent(() -> new 
Label("id", "Hello renderer"));
+               
+               assertEquals("Hello renderer", html.toString());
+       }
+}
\ No newline at end of file

Reply via email to