org.apache.wicket.markup.MarkupFactory.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.wicket.markup.MarkupFactory.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.markup;

import java.io.IOException;

import org.apache.wicket.Application;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.markup.loader.DefaultMarkupLoader;
import org.apache.wicket.markup.loader.IMarkupLoader;
import org.apache.wicket.markup.parser.IMarkupFilter;
import org.apache.wicket.markup.parser.IXmlPullParser;
import org.apache.wicket.markup.parser.XmlPullParser;
import org.apache.wicket.util.lang.Args;
import org.apache.wicket.util.resource.IResourceStream;
import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Factory to load markup either from cache or from a resource.
 * <p>
 * This class is the main entry point to load markup. Nothing else should be required by Components.
 * It manages caching markup as well as loading and merging (inheritance) of markup.
 * <p>
 * The markup returned is immutable as it gets re-used across multiple Component instances.
 * 
 * @author Juergen Donnerstag
 */
public class MarkupFactory {
    /** Log for reporting. */
    private static final Logger log = LoggerFactory.getLogger(MarkupFactory.class);

    /** A markup cache */
    private IMarkupCache markupCache = null;

    /** The markup resource stream provider used by MarkupCache */
    private IMarkupResourceStreamProvider markupResourceStreamProvider = null;

    /**
     * @return Gets the markup factory registered with the Wicket application
     */
    public static MarkupFactory get() {
        return Application.get().getMarkupSettings().getMarkupFactory();
    }

    /**
     * Construct.
     */
    public MarkupFactory() {
    }

    /**
     * MarkupLoaders are responsible to find and load the markup for a component. That may be a
     * single file, but e.g. like in markup inheritance it could also be that the markup from
     * different sources must be merged.
     * 
     * @return By default an instance of {@link DefaultMarkupLoader} will be returned. Via
     *         subclassing you may return your own markup loader (chain).
     */
    public IMarkupLoader getMarkupLoader() {
        return new DefaultMarkupLoader();
    }

    /**
     * Create a new markup parser. Markup parsers read the markup and dissect it in Wicket relevant
     * pieces {@link MarkupElement}'s (kind of Wicket's DOM).
     * <p>
     * MarkupParser's can be extended by means of {@link IMarkupFilter}. You can add your own filter
     * as follows:
     * 
     * <pre>
     *    public MyMarkupFactory {
     *      ...
     *      public MarkupParser newMarkupParser(final MarkupResourceStream resource) {
     *         MarkupParser parser = super.newMarkupParser(resource);
     *         parser.add(new MyFilter());
     *         return parser;
     *      }
     *    }
     * </pre>
     * 
     * @see #onAppendMarkupFilter(IMarkupFilter)
     * 
     * @param resource
     *            The resource containing the markup
     * @return A fresh instance of {@link MarkupParser}
     */
    public MarkupParser newMarkupParser(final MarkupResourceStream resource) {
        // Markup parsers can not be re-used
        return new MarkupParser(newXmlPullParser(), resource) {
            @Override
            protected IMarkupFilter onAppendMarkupFilter(final IMarkupFilter filter) {
                return MarkupFactory.this.onAppendMarkupFilter(filter);
            }
        };
    }

    /**
     * Subclasses can override this to use custom parsers.
     * 
     * @return parser instance used by {@link MarkupParser} to parse markup.
     */
    protected IXmlPullParser newXmlPullParser() {
        return new XmlPullParser();
    }

    /**
     * A callback method that is invoked prior to any {@link IMarkupFilter} being registered with
     * {@link MarkupParser}. Hence it allows to:
     * <ul>
     * <li>tweak the default configuration of a filter</li>
     * <li>replace a filter with another one</li>
     * <li>avoid filters being used by returning null</li>
     * </ul>
     * Note that a new {@link MarkupParser} instance is created for each markup resources being
     * loaded.
     * <p>
     * 
     * @param filter
     *            The filter to be registered with the MarkupParser
     * @return The filter to be added. Null to ignore.
     */
    protected IMarkupFilter onAppendMarkupFilter(final IMarkupFilter filter) {
        return filter;
    }

    /**
     * Get the markup cache which is registered with the factory. Since the factory is registered
     * with the application, only one cache per application exists.
     * <p>
     * Please note that markup cache is a pull through cache. It'll invoke a factory method
     * {@link #getMarkupResourceStream(MarkupContainer, Class)} to load the markup if not yet
     * available in the cache.
     * 
     * @return Null, to disable caching.
     */
    public IMarkupCache getMarkupCache() {
        if (markupCache == null) {
            markupCache = new MarkupCache();
        }

        return markupCache;
    }

    /**
     * @return <code>true</code> if markup cache is available. Make sure you called
     *         {@link #getMarkupCache()} at least once before to initialize the cache.
     */
    public boolean hasMarkupCache() {
        return markupCache != null;
    }

    /**
     * Get the markup associated with the container.
     * 
     * @param container
     *            The container to find the markup for
     * @param enforceReload
     *            If true, the cache will be ignored and all, including inherited markup files, will
     *            be reloaded. Whatever is in the cache, it will be ignored
     * @return The markup associated with the container. Null, if the markup was not found or could
     *         not yet be loaded (e.g. getMarkupType() == null). Wicket Exception in case of errors.
     */
    public final Markup getMarkup(final MarkupContainer container, final boolean enforceReload) {
        return getMarkup(container, container.getClass(), enforceReload);
    }

    /**
     * Get the markup associated with the container. Check the cache first. If not found, than load
     * the markup and update the cache.
     * <p>
     * The clazz parameter usually can be null, except for base (inherited) markup.
     * <p>
     * There are several means to disable markup caching. Caching can be disabled alltogether -
     * getMarkupCache() return null -, or individually (cacheKey == null).
     * 
     * @param container
     *            The container to find the markup for
     * @param clazz
     *            Must be the container class or any of its super classes. May be null.
     * @param enforceReload
     *            The cache will be ignored and all, including inherited markup files, will be
     *            reloaded. Whatever is in the cache, it will be ignored
     * @return The markup associated with the container. Null, if the markup was not found or could
     *         not yet be loaded (e.g. getMarkupType() == null). Wicket Exception in case of errors.
     */
    public final Markup getMarkup(final MarkupContainer container, final Class<?> clazz,
            final boolean enforceReload) {
        Args.notNull(container, "container");

        if (checkMarkupType(container) == false) {
            // TODO improve: Result { boolean success, enum FailureReason {not found, not yet
            // available}, Markup markup }
            return null;
        }

        Class<?> containerClass = getContainerClass(container, clazz);

        IMarkupCache cache = getMarkupCache();
        if (cache != null) {
            // MarkupCache acts as pull-through cache. It'll call the same loadMarkup() method as
            // below, if needed.
            // @TODO may be that can be changed. I don't like it too much.
            return cache.getMarkup(container, containerClass, enforceReload);
        }

        // Get the markup resource stream for the container (and super class)
        MarkupResourceStream markupResourceStream = getMarkupResourceStream(container, containerClass);

        return loadMarkup(container, markupResourceStream, enforceReload);
    }

    /**
     * Without a markup type we can not search for a file and we can not construct the cacheKey. We
     * can not even load associated markup as required for Panels. Though every MarkupContainer can
     * provide it's own type, by default they refer to the Page. Hence, no markup type is an
     * indicator, that the component or any of its parents, has not yet been added.
     * 
     * @param container
     *          The MarkupContainer which markup type has to checked
     * @return true, if container.getMarkupType() != null
     */
    protected final boolean checkMarkupType(final MarkupContainer container) {
        if (container.getMarkupType() == null) {
            log.debug("Markup file not loaded, since the markup type is not yet available: {}", container);
            return false;
        }

        return true;
    }

    /**
     * Get the markup resource stream provider registered with the factory.
     * <p>
     * If the 'container' implements {@link IMarkupResourceStreamProvider}, the container itself
     * will be asked to provide the resource stream. Else Wicket's default implementation will be
     * used.
     * 
     * @param container
     *            The MarkupContainer requesting the markup resource stream
     * @return IMarkupResourceStreamProvider
     */
    protected final IMarkupResourceStreamProvider getMarkupResourceStreamProvider(final MarkupContainer container) {
        if (container instanceof IMarkupResourceStreamProvider) {
            return (IMarkupResourceStreamProvider) container;
        }

        if (markupResourceStreamProvider == null) {
            markupResourceStreamProvider = new DefaultMarkupResourceStreamProvider();
        }
        return markupResourceStreamProvider;
    }

    /**
     * Create a new markup resource stream for the container and optionally the Class. The Class
     * must be provided in case of base (inherited) markup. Else it might be null (standard use
     * case).
     * 
     * @param container
     *            The MarkupContainer which requests to load the Markup resource stream
     * @param clazz
     *            Either the container class or any super class. Might be null.
     * @return A IResourceStream if the resource was found
     */
    public final MarkupResourceStream getMarkupResourceStream(final MarkupContainer container, Class<?> clazz) {
        Args.notNull(container, "container");

        if (checkMarkupType(container) == false) {
            // TODO improve: Result { boolean success, enum FailureReason {not found, not yet
            // available}, Markup markup }
            return null;
        }

        Class<?> containerClass = getContainerClass(container, clazz);

        // Who is going to provide the markup resource stream?
        // And ask the provider to locate the markup resource stream
        final IResourceStream resourceStream = getMarkupResourceStreamProvider(container)
                .getMarkupResourceStream(container, containerClass);

        // Found markup?
        if (resourceStream == null) {
            // TODO improve: Result { boolean success, enum FailureReason {not found, not yet
            // available}, Markup markup }
            return null;
        }

        if (resourceStream instanceof MarkupResourceStream) {
            return (MarkupResourceStream) resourceStream;
        }

        return new MarkupResourceStream(resourceStream, new ContainerInfo(container), containerClass);
    }

    /**
     * Gets and checks the container class
     * 
     * @param container
     *            The MarkupContainer which requests to load the Markup resource stream
     * @param clazz
     *            Either null, or a super class of container
     * @return The container class to be used
     */
    public final Class<?> getContainerClass(final MarkupContainer container, final Class<?> clazz) {
        Args.notNull(container, "container");

        Class<?> containerClass = clazz;
        if (clazz == null) {
            containerClass = container.getClass();
        } else if (!clazz.isAssignableFrom(container.getClass())) {
            throw new IllegalArgumentException("Parameter clazz must be an instance of "
                    + container.getClass().getName() + ", but is a " + clazz.getName());
        }
        return containerClass;
    }

    /**
     * Loads markup from a resource stream. It'll call the registered markup loader to load the
     * markup.
     * <p>
     * Though the 'enforceReload' attribute seem to imply that the cache is consulted to retrieve
     * the markup, the cache in fact is only checked for retrieving the base (inherited) markup.
     * Please see {@link #getMarkup(MarkupContainer, boolean)} as well.
     * 
     * @param container
     *            The original requesting markup container
     * @param markupResourceStream
     *            The markup resource stream to load, if already known.
     * @param enforceReload
     *            The cache will be ignored and all, including inherited markup files, will be
     *            reloaded. Whatever is in the cache, it will be ignored
     * @return The markup. Null, if the markup was not found. Wicket Exception in case of errors.
     */
    public final Markup loadMarkup(final MarkupContainer container, final MarkupResourceStream markupResourceStream,
            final boolean enforceReload) {
        // @TODO can markupResourceStream be replace with clazz???
        Args.notNull(container, "container");
        Args.notNull(markupResourceStream, "markupResourceStream");

        if (checkMarkupType(container) == false) {
            // TODO improve: Result { boolean success, enum FailureReason {not found, not yet
            // available}, Markup markup }
            return null;
        }

        try {
            // The InheritedMarkupMarkupLoader needs to load the base markup. It'll do it via
            // MarkupFactory.getMarkup() as main entry point, which in turn allows to choose between
            // use or ignore the cache. That's why we need to propagate enforceReload to the markup
            // loader as well.

            // Markup loader is responsible to load the full markup for the container. In case of
            // markup inheritance, the markup must be merged from different markup files. It is the
            // merged markup which eventually will be cached, thus avoiding repetitive merge
            // operations, which always result in the same outcome.
            // The base markup will still be cached though, in order to avoid any unnecessary
            // reloads. The base markup itself might be merged as it might inherit from its base
            // class.

            return getMarkupLoader().loadMarkup(container, markupResourceStream, null, enforceReload);
        } catch (MarkupNotFoundException e) {
            // InheritedMarkupMarkupLoader will throw a MarkupNotFoundException in case the
            // <b>base</b> markup can not be found.

            log.error("Markup not found: " + e.getMessage(), e);

            // Catch exception and ignore => return null (markup not found)
        } catch (ResourceStreamNotFoundException e) {
            log.error("Markup not found: " + markupResourceStream, e);

            // Catch exception and ignore => return null (markup not found)
        } catch (IOException e) {
            log.error("Error while reading the markup " + markupResourceStream, e);

            // Wrap with wicket exception and re-throw
            throw new MarkupException(markupResourceStream, "IO error while reading markup: " + e.getMessage(), e);
        } catch (WicketRuntimeException e) {
            log.error("Error while reading the markup " + markupResourceStream, e);

            // re-throw
            throw e;
        } catch (RuntimeException e) {
            log.error("Error while reading the markup " + markupResourceStream, e);

            // Wrap with wicket exception and re-throw
            throw new MarkupException(markupResourceStream, "Error while reading the markup: " + e.getMessage(), e);
        }

        // Markup not found. Errors should throw a Wicket exception
        return null;
    }
}