org.apache.wicket.markup.MarkupFactory Maven / Gradle / Ivy
Show all versions of org.ops4j.pax.wicket.service Show documentation
/*
* 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.
*
* 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.
*
* 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;
/** The markup resource stream provider used by MarkupCache */
private IMarkupResourceStreamProvider markupResourceStreamProvider;
/**
* @return Gets the markup factory registered with the Wicket application
*/
public final 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).
*
* MarkupParser's can be extended by means of {@link IMarkupFilter}. You can add your own filter
* as follows:
*
*
* public MyMarkupFactory {
* ...
* public MarkupParser newMarkupParser(final MarkupResourceStream resource) {
* MarkupParser parser = super.newMarkupParser(resource);
* parser.add(new MyFilter());
* return parser;
* }
* }
*
*
* @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:
*
* - tweak the default configuration of a filter
* - replace a filter with another one
* - avoid filters being used by returning null
*
* Note that a new {@link MarkupParser} instance is created for each markup resources being
* loaded.
*
*
* @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.
*
* 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 true
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.
*
* The clazz parameter usually can be null, except for base (inherited) markup.
*
* 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
* @return true, if container.getMarkupType() != null
*/
protected final boolean checkMarkupType(final MarkupContainer container)
{
if (container.getMarkupType() == null)
{
if (log.isDebugEnabled())
{
log.debug("Markup file not loaded, since the markup type is not yet available: " +
container.toString());
}
return false;
}
return true;
}
/**
* Check if container has associated markup
*
* @param container
* The container to find the markup for
* @return True if this markup container has associated markup
* @deprecated please use {@link #getMarkup(MarkupContainer, boolean)} instead
*/
@Deprecated
public final boolean hasAssociatedMarkup(final MarkupContainer container)
{
Markup markup = getMarkup(container, false);
return markup != null && markup != Markup.NO_MARKUP;
}
/**
* Get the markup resource stream provider registered with the factory.
*
* 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
* @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.
*
* 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
// base 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 readin 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;
}
}