You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@wicket.apache.org by svenmeier <gi...@git.apache.org> on 2017/12/07 08:54:11 UTC

[GitHub] wicket pull request #249: independent component renderer

GitHub user svenmeier opened a pull request:

    https://github.com/apache/wicket/pull/249

    independent component renderer

    ComponentRenderer makes it easy to render components, but it still needs the thread to be attached to a WebApplication's ThreadContext: I've seen projects use WicketTester for that or rolling their own solution.
    
    IMHO we could improve ComponentRender to officially support independent rendering of components.
    
    WDYT?

You can merge this pull request into a Git repository by running:

    $ git pull https://github.com/apache/wicket sandbox/component-renderer

Alternatively you can review and apply these changes as the patch at:

    https://github.com/apache/wicket/pull/249.patch

To close this pull request, make a commit to your master/trunk branch
with (at least) the following in the commit message:

    This closes #249
    
----
commit 6e6c273fd32325a447cc32c751d5ce0c083e7ed1
Author: Sven Meier <sv...@apache.org>
Date:   2017-12-07T08:24:12Z

    render independently from web or tester

commit 0baab8c3027bbaafb1a218f3cf87edf0040d856f
Author: Sven Meier <sv...@apache.org>
Date:   2017-12-07T08:44:47Z

    use lambdas, fixed imports

----


---

[GitHub] wicket pull request #249: independent component renderer

Posted by asfgit <gi...@git.apache.org>.
Github user asfgit closed the pull request at:

    https://github.com/apache/wicket/pull/249


---

[GitHub] wicket issue #249: independent component renderer

Posted by solomax <gi...@git.apache.org>.
Github user solomax commented on the issue:

    https://github.com/apache/wicket/pull/249
  
    We are currently using WicketTester for this, I'll try this later tonight and will give huge +1 :))


---

[GitHub] wicket pull request #249: independent component renderer

Posted by martin-g <gi...@git.apache.org>.
Github user martin-g commented on a diff in the pull request:

    https://github.com/apache/wicket/pull/249#discussion_r155464694
  
    --- Diff: wicket-core/src/main/java/org/apache/wicket/core/util/string/ComponentRenderer.java ---
    @@ -16,35 +16,321 @@
      */
     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.request.http.WebRequest;
    +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;
     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 defaults of
    + * {@link #ComponentRenderer()} for a mocked application with sensible defaults.
    + * <p>
    + * Note: For performance 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 WebApplication 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());
    +			}
    +		});
    +	}
    +
    +	/**
    +	 * 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.
    +	 */
    +	public ComponentRenderer(WebApplication application)
    +	{
    +		this.application = application;
    +
    +		if (application.getName() == null)
    +		{
    +			// not yet initialized
    +
    +			inThreadContext(() -> {
    +				application.setServletContext(new MockServletContext(application, null));
    +				application.setName(
    +					"ComponentRenderer[" + System.identityHashCode(ComponentRenderer.this) + "]");
    +				application.initApplication();
    +			});
    +		}
    +	}
    +
    +	/**
    +	 * Destroy this renderer.
    +	 */
    +	public void destroy()
    +	{
    +		inThreadContext(() -> {
    +			application.internalDestroy();
    +		});
    +	}
    +
    +	/**
    +	 * 
    +	 * Collects the html generated by the rendering a component.
    +	 * 
    +	 * @param component
    +	 *            supplier of the component
    +	 * @return the html rendered by the panel
    +	 */
    +	public CharSequence renderComponent(final Supplier<Component> component)
    +	{
    +		return renderPage(() -> new RenderPage(component.get()));
    +	}
    +
    +	/**
    +	 * Collects the html generated by the rendered a component.
    +	 *
    +	 * @param page
    +	 *            supplier of the page
    +	 * @return the html rendered by the panel
    +	 */
    +	public CharSequence renderPage(final Supplier<? extends Page> page)
    +	{
    +		return inThreadContext(() -> {
    +			WebRequest request = newWebRequest();
    +
    +			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 valid {@link ThreadContext}.
    +	 * 
    +	 * @param runnable
    +	 *            runnable
    +	 */
    +	private void inThreadContext(Runnable runnable)
    +	{
    +		inThreadContext(() -> {
    +			runnable.run();
    +			return null;
    +		});
    +	}
    +
    +	/**
    +	 * Get the result from the given supplier inside a valid {@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 WebRequest newWebRequest()
    +	{
    +		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 of a page.
    +	 * <p>
    +	 * Important note: Must be called on a thread already processing a {@link WebApplication}!
    --- End diff --
    
    What does it mean "a thread processing an application" ?
    Processing a request is clear, but application is not.


---

[GitHub] wicket pull request #249: independent component renderer

Posted by solomax <gi...@git.apache.org>.
Github user solomax commented on a diff in the pull request:

    https://github.com/apache/wicket/pull/249#discussion_r155473193
  
    --- Diff: wicket-core/src/main/java/org/apache/wicket/core/util/string/ComponentRenderer.java ---
    @@ -16,35 +16,321 @@
      */
     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.request.http.WebRequest;
    +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;
     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 defaults of
    + * {@link #ComponentRenderer()} for a mocked application with sensible defaults.
    + * <p>
    + * Note: For performance 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 WebApplication 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());
    +			}
    +		});
    +	}
    +
    +	/**
    +	 * 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.
    +	 */
    +	public ComponentRenderer(WebApplication application)
    +	{
    +		this.application = application;
    +
    +		if (application.getName() == null)
    +		{
    +			// not yet initialized
    +
    +			inThreadContext(() -> {
    +				application.setServletContext(new MockServletContext(application, null));
    +				application.setName(
    +					"ComponentRenderer[" + System.identityHashCode(ComponentRenderer.this) + "]");
    +				application.initApplication();
    +			});
    +		}
    +	}
    +
    +	/**
    +	 * Destroy this renderer.
    +	 */
    +	public void destroy()
    +	{
    +		inThreadContext(() -> {
    +			application.internalDestroy();
    +		});
    +	}
    +
    +	/**
    +	 * 
    +	 * Collects the html generated by the rendering a component.
    +	 * 
    +	 * @param component
    +	 *            supplier of the component
    +	 * @return the html rendered by the panel
    +	 */
    +	public CharSequence renderComponent(final Supplier<Component> component)
    +	{
    +		return renderPage(() -> new RenderPage(component.get()));
    +	}
    +
    +	/**
    +	 * Collects the html generated by the rendered a component.
    +	 *
    +	 * @param page
    +	 *            supplier of the page
    +	 * @return the html rendered by the panel
    +	 */
    +	public CharSequence renderPage(final Supplier<? extends Page> page)
    +	{
    +		return inThreadContext(() -> {
    +			WebRequest request = newWebRequest();
    +
    +			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 valid {@link ThreadContext}.
    +	 * 
    +	 * @param runnable
    +	 *            runnable
    +	 */
    +	private void inThreadContext(Runnable runnable)
    +	{
    +		inThreadContext(() -> {
    +			runnable.run();
    +			return null;
    +		});
    +	}
    +
    +	/**
    +	 * Get the result from the given supplier inside a valid {@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 WebRequest newWebRequest()
    +	{
    +		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 of a page.
    +	 * <p>
    +	 * Important note: Must be called on a thread already processing a {@link WebApplication}!
    --- End diff --
    
    ups `static`, sorry :(


---

[GitHub] wicket pull request #249: independent component renderer

Posted by solomax <gi...@git.apache.org>.
Github user solomax commented on a diff in the pull request:

    https://github.com/apache/wicket/pull/249#discussion_r155466396
  
    --- Diff: wicket-core/src/main/java/org/apache/wicket/core/util/string/ComponentRenderer.java ---
    @@ -16,35 +16,321 @@
      */
     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.request.http.WebRequest;
    +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;
     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 defaults of
    + * {@link #ComponentRenderer()} for a mocked application with sensible defaults.
    + * <p>
    + * Note: For performance 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 WebApplication 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());
    +			}
    +		});
    +	}
    +
    +	/**
    +	 * 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.
    +	 */
    +	public ComponentRenderer(WebApplication application)
    +	{
    +		this.application = application;
    +
    +		if (application.getName() == null)
    +		{
    +			// not yet initialized
    +
    +			inThreadContext(() -> {
    +				application.setServletContext(new MockServletContext(application, null));
    +				application.setName(
    +					"ComponentRenderer[" + System.identityHashCode(ComponentRenderer.this) + "]");
    +				application.initApplication();
    +			});
    +		}
    +	}
    +
    +	/**
    +	 * Destroy this renderer.
    +	 */
    +	public void destroy()
    +	{
    +		inThreadContext(() -> {
    +			application.internalDestroy();
    +		});
    +	}
    +
    +	/**
    +	 * 
    +	 * Collects the html generated by the rendering a component.
    +	 * 
    +	 * @param component
    +	 *            supplier of the component
    +	 * @return the html rendered by the panel
    +	 */
    +	public CharSequence renderComponent(final Supplier<Component> component)
    +	{
    +		return renderPage(() -> new RenderPage(component.get()));
    +	}
    +
    +	/**
    +	 * Collects the html generated by the rendered a component.
    +	 *
    +	 * @param page
    +	 *            supplier of the page
    +	 * @return the html rendered by the panel
    +	 */
    +	public CharSequence renderPage(final Supplier<? extends Page> page)
    +	{
    +		return inThreadContext(() -> {
    +			WebRequest request = newWebRequest();
    +
    +			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 valid {@link ThreadContext}.
    +	 * 
    +	 * @param runnable
    +	 *            runnable
    +	 */
    +	private void inThreadContext(Runnable runnable)
    +	{
    +		inThreadContext(() -> {
    +			runnable.run();
    +			return null;
    +		});
    +	}
    +
    +	/**
    +	 * Get the result from the given supplier inside a valid {@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 WebRequest newWebRequest()
    +	{
    +		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 of a page.
    +	 * <p>
    +	 * Important note: Must be called on a thread already processing a {@link WebApplication}!
    --- End diff --
    
    `Application.get()` will fail in case current thread doesn't owns Application ....
    
    Why `Application.get()` is being used here, and not `this.application` ?


---

[GitHub] wicket pull request #249: independent component renderer

Posted by martin-g <gi...@git.apache.org>.
Github user martin-g commented on a diff in the pull request:

    https://github.com/apache/wicket/pull/249#discussion_r155481407
  
    --- Diff: wicket-core/src/main/java/org/apache/wicket/core/util/string/ComponentRenderer.java ---
    @@ -16,35 +16,321 @@
      */
     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.request.http.WebRequest;
    +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;
     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 defaults of
    + * {@link #ComponentRenderer()} for a mocked application with sensible defaults.
    + * <p>
    + * Note: For performance 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 WebApplication 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());
    +			}
    +		});
    +	}
    +
    +	/**
    +	 * 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.
    +	 */
    +	public ComponentRenderer(WebApplication application)
    +	{
    +		this.application = application;
    +
    +		if (application.getName() == null)
    +		{
    +			// not yet initialized
    +
    +			inThreadContext(() -> {
    +				application.setServletContext(new MockServletContext(application, null));
    +				application.setName(
    +					"ComponentRenderer[" + System.identityHashCode(ComponentRenderer.this) + "]");
    +				application.initApplication();
    +			});
    +		}
    +	}
    +
    +	/**
    +	 * Destroy this renderer.
    +	 */
    +	public void destroy()
    +	{
    +		inThreadContext(() -> {
    +			application.internalDestroy();
    +		});
    +	}
    +
    +	/**
    +	 * 
    +	 * Collects the html generated by the rendering a component.
    +	 * 
    +	 * @param component
    +	 *            supplier of the component
    +	 * @return the html rendered by the panel
    +	 */
    +	public CharSequence renderComponent(final Supplier<Component> component)
    +	{
    +		return renderPage(() -> new RenderPage(component.get()));
    +	}
    +
    +	/**
    +	 * Collects the html generated by the rendered a component.
    +	 *
    +	 * @param page
    +	 *            supplier of the page
    +	 * @return the html rendered by the panel
    +	 */
    +	public CharSequence renderPage(final Supplier<? extends Page> page)
    +	{
    +		return inThreadContext(() -> {
    +			WebRequest request = newWebRequest();
    +
    +			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 valid {@link ThreadContext}.
    +	 * 
    +	 * @param runnable
    +	 *            runnable
    +	 */
    +	private void inThreadContext(Runnable runnable)
    +	{
    +		inThreadContext(() -> {
    +			runnable.run();
    +			return null;
    +		});
    +	}
    +
    +	/**
    +	 * Get the result from the given supplier inside a valid {@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 WebRequest newWebRequest()
    +	{
    +		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 of a page.
    +	 * <p>
    +	 * Important note: Must be called on a thread already processing a {@link WebApplication}!
    --- End diff --
    
    > what about 'a thread bound to a wicket ThreadContext'
    
    Yes, this is more clear, to me at least!


---

[GitHub] wicket pull request #249: independent component renderer

Posted by svenmeier <gi...@git.apache.org>.
Github user svenmeier commented on a diff in the pull request:

    https://github.com/apache/wicket/pull/249#discussion_r155472740
  
    --- Diff: wicket-core/src/main/java/org/apache/wicket/core/util/string/ComponentRenderer.java ---
    @@ -16,35 +16,321 @@
      */
     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.request.http.WebRequest;
    +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;
     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 defaults of
    + * {@link #ComponentRenderer()} for a mocked application with sensible defaults.
    + * <p>
    + * Note: For performance 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 WebApplication 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());
    +			}
    +		});
    +	}
    +
    +	/**
    +	 * 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.
    +	 */
    +	public ComponentRenderer(WebApplication application)
    +	{
    +		this.application = application;
    +
    +		if (application.getName() == null)
    +		{
    +			// not yet initialized
    +
    +			inThreadContext(() -> {
    +				application.setServletContext(new MockServletContext(application, null));
    +				application.setName(
    +					"ComponentRenderer[" + System.identityHashCode(ComponentRenderer.this) + "]");
    +				application.initApplication();
    +			});
    +		}
    +	}
    +
    +	/**
    +	 * Destroy this renderer.
    +	 */
    +	public void destroy()
    +	{
    +		inThreadContext(() -> {
    +			application.internalDestroy();
    +		});
    +	}
    +
    +	/**
    +	 * 
    +	 * Collects the html generated by the rendering a component.
    +	 * 
    +	 * @param component
    +	 *            supplier of the component
    +	 * @return the html rendered by the panel
    +	 */
    +	public CharSequence renderComponent(final Supplier<Component> component)
    +	{
    +		return renderPage(() -> new RenderPage(component.get()));
    +	}
    +
    +	/**
    +	 * Collects the html generated by the rendered a component.
    +	 *
    +	 * @param page
    +	 *            supplier of the page
    +	 * @return the html rendered by the panel
    +	 */
    +	public CharSequence renderPage(final Supplier<? extends Page> page)
    +	{
    +		return inThreadContext(() -> {
    +			WebRequest request = newWebRequest();
    +
    +			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 valid {@link ThreadContext}.
    +	 * 
    +	 * @param runnable
    +	 *            runnable
    +	 */
    +	private void inThreadContext(Runnable runnable)
    +	{
    +		inThreadContext(() -> {
    +			runnable.run();
    +			return null;
    +		});
    +	}
    +
    +	/**
    +	 * Get the result from the given supplier inside a valid {@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 WebRequest newWebRequest()
    +	{
    +		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 of a page.
    +	 * <p>
    +	 * Important note: Must be called on a thread already processing a {@link WebApplication}!
    --- End diff --
    
    @solomax this is the 'old' static method that reuses whatever application is bound to the thread
    @martin-g what about 'a thread bound to a wicket ThreadContext' 


---