org.apache.wicket.core.util.string.ComponentRenderer.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.wicket.core.util.string.ComponentRenderer.java

Source

/*
 * 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;
        }
    }

}