Java tutorial
/* * 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; 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; import org.slf4j.LoggerFactory; /** * A helper class for rendering components and pages. * <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()); } }); } /** * 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) { Application application = Application.get(); RequestCycle originalRequestCycle = RequestCycle.get(); BufferedWebResponse tempResponse = new BufferedWebResponse(null); RequestCycle tempRequestCycle = application.createRequestCycle(originalRequestCycle.getRequest(), tempResponse); try { ThreadContext.setRequestCycle(tempRequestCycle); pageProvider.getPageInstance().renderPage(); } finally { ThreadContext.setRequestCycle(originalRequestCycle); } return tempResponse.getText(); } /** * Collects the Html generated by rendering a component. * <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) { RequestCycle requestCycle = RequestCycle.get(); final Response originalResponse = requestCycle.getResponse(); BufferedWebResponse tempResponse = new BufferedWebResponse(null); MarkupContainer oldParent = component.getParent(); 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); } try { requestCycle.setResponse(tempResponse); // add the component to a dummy page just for the rendering RenderPage page = new RenderPage(component); page.internalInitialize(); component.beforeRender(); component.renderPart(); } finally { if (oldParent != null) { oldParent.add(component); // re-add the child to its old parent } requestCycle.setResponse(originalResponse); } return tempResponse.getText(); } /** * A page used as a parent for the component based rendering. */ private static class RenderPage extends WebPage implements IMarkupResourceStreamProvider, IMarkupCacheKeyProvider { /** * Markup to use when the component to render is not already added to a MarkupContainer */ private static final String DEFAULT_MARKUP = "<wicket:container wicket:id='%s'></wicket:container>"; /** * The markup of the component to render */ private final String markup; private RenderPage(Component component) { // do not store the page in IPageStore/IDataStore. WICKET-5422 setStatelessHint(true); String componentMarkup; try { componentMarkup = component.getMarkup().toString(true); } catch (MarkupNotFoundException mnfx) { componentMarkup = String.format(DEFAULT_MARKUP, component.getId()); } this.markup = componentMarkup; add(component); // this changes the component's parent } @Override public IResourceStream getMarkupResourceStream(MarkupContainer container, Class<?> containerClass) { return new StringResourceStream(markup); } @Override public String getCacheKey(MarkupContainer container, Class<?> containerClass) { // no caching for this page return null; } @Override public boolean isBookmarkable() { // pretend the page is bookmarkable to make it stateless. WICKET-5422 return true; } } }