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 :(
---