org.apache.wicket.markup.resolver.AutoLinkResolver 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.resolver;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.Page;
import org.apache.wicket.application.IClassResolver;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.IMarkupFragment;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
import org.apache.wicket.markup.html.link.ExternalLink;
import org.apache.wicket.markup.parser.filter.WicketLinkTagHandler;
import org.apache.wicket.protocol.http.RequestUtils;
import org.apache.wicket.request.handler.resource.ResourceReferenceRequestHandler;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.request.resource.PackageResource;
import org.apache.wicket.request.resource.PackageResourceReference;
import org.apache.wicket.request.resource.ResourceReference;
import org.apache.wicket.util.lang.Packages;
import org.apache.wicket.util.string.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The AutoLinkResolver is responsible to handle automatic link resolution. Tags are marked
* "autolink" by the MarkupParser for all tags with href attribute, such as anchor and link tags
* with no explicit wicket id. E.g. <a href="Home.html">
*
* If href points to a *.html file, a BookmarkablePageLink> will automatically be created, except
* for absolute paths, where an ExternalLink is created.
*
* If href points to a *.html file, it resolves the given URL by searching for a page class, either
* relative or absolute, specified by the href attribute of the tag. If relative the href URL must
* be relative to the package containing the associated page. An exception is thrown if no Page
* class was found.
*
* If href is no *.html file a static reference to the resource is created.
*
* @see org.apache.wicket.markup.parser.filter.WicketLinkTagHandler
*
* @author Juergen Donnerstag
* @author Eelco Hillenius
*/
public final class AutoLinkResolver implements IComponentResolver
{
/**
* Abstract implementation that has a helper method for creating a resource reference.
*/
public static abstract class AbstractAutolinkResolverDelegate
implements
IAutolinkResolverDelegate
{
/**
* Creates a new auto component that references a package resource.
*
* @param container
* the parent container
* @param autoId
* the automatically generated id for the auto component
* @param pathInfo
* the path info object that contains information about the link reference
* @param attribute
* the attribute to replace the value of
* @return a new auto component or null if the path was absolute
*/
protected final Component newPackageResourceReferenceAutoComponent(
final MarkupContainer container, final String autoId, final PathInfo pathInfo,
final String attribute)
{
if (!pathInfo.absolute && (pathInfo.path != null) && (pathInfo.path.length() > 0))
{
// Href is relative. Create a resource reference pointing at this file
// components are handled differently. We can
// not use the container, because it is the container the
// header has been added to (e.g. the Page). What we need
// however, is the component (e.g. a Panel) which
// contributed it.
MarkupStream markupStream = new MarkupStream(container.getMarkup());
Class extends Component> clazz = markupStream.getContainerClass();
// However if the markup stream is a merged markup stream (inheritance), than we
// need the class of the markup file which contained the tag.
if ((markupStream.get() instanceof ComponentTag) &&
(markupStream.getTag().getMarkupClass() != null))
{
clazz = markupStream.getTag().getMarkupClass();
}
// Create the component implementing the link
ResourceReferenceAutolink autoLink = new ResourceReferenceAutolink(autoId, clazz,
pathInfo.reference, attribute, container);
if (autoLink.resourceReference != null)
{
// if the resource reference is null, it means that it the
// reference was not found as a package resource
return autoLink;
}
}
// else we can't have absolute resource references, at least not at
// this time
// fall back on default processing
return null;
}
}
/**
* Autolink components delegate component resolution to their parent components. Reason:
* autolink tags don't have wicket:id and users wouldn't know where to add the component to.
*
* @author Juergen Donnerstag
* @param
* type of model object
*/
public final static class AutolinkBookmarkablePageLink extends BookmarkablePageLink
implements
IComponentResolver
{
private static final long serialVersionUID = 1L;
private final String anchor;
/**
* When using to let Wicket lookup for pages and create the related links,
* it's not possible to change the "setAutoEnable" property, which defaults to true. This
* affects the prototype because, sometimes designers _want_ links to be enabled.
*/
public static boolean autoEnable = true;
/**
* Construct
*
* @param
*
* @see BookmarkablePageLink#BookmarkablePageLink(String, Class, PageParameters)
*
* @param id
* @param pageClass
* @param parameters
* @param anchor
*/
public AutolinkBookmarkablePageLink(final String id,
final Class pageClass, final PageParameters parameters, final String anchor)
{
super(id, pageClass, parameters);
this.anchor = anchor;
setAutoEnable(autoEnable);
}
/**
*
* @see org.apache.wicket.markup.html.link.BookmarkablePageLink#getURL()
*/
@Override
protected CharSequence getURL()
{
CharSequence url = super.getURL();
if (anchor != null)
{
url = url + anchor;
}
return url;
}
/**
* @see org.apache.wicket.markup.resolver.IComponentResolver#resolve(org.apache.wicket.MarkupContainer,
* org.apache.wicket.markup.MarkupStream, org.apache.wicket.markup.ComponentTag)
*/
public Component resolve(final MarkupContainer container, final MarkupStream markupStream,
ComponentTag tag)
{
return getParent().get(tag.getId());
}
}
/**
* Interface to delegate the actual resolving of auto components to.
*/
public static interface IAutolinkResolverDelegate
{
/**
* Returns a new auto component based on the pathInfo object. The auto component must have
* the autoId assigned as it's id. Should return null in case the component could not be
* created as expected and the default resolving should take place.
*
* @param container
* the parent container
* @param autoId
* the automatically generated id for the auto component
* @param pathInfo
* the path info object that contains information about the link reference
* @return a new auto component or null in case this method couldn't resolve to a proper
* auto component
*/
Component newAutoComponent(final MarkupContainer container, final String autoId,
final PathInfo pathInfo);
}
/**
* Encapsulates different aspects of a path. For instance, the path
* org.apache.wicket.markup.html.tree.Tree/tree.css
has extension css
,
* is relative (absolute == true) and has no page parameters.
*/
public static final class PathInfo
{
/** whether the reference is absolute. */
private final boolean absolute;
/** An optional anchor like #top */
private final String anchor;
/** The extension if any. */
private final String extension;
/** The optional page parameters. */
private final PageParameters pageParameters;
/** The path excluding any parameters. */
private final String path;
/** The original reference (e.g the full value of a href attribute). */
private final String reference;
/**
* Construct.
*
* @param reference
* the original reference (e.g the full value of a href attribute)
*/
public PathInfo(final String reference)
{
this.reference = reference;
// If href contains URL query parameters ..
String infoPath;
// get the query string
int queryStringPos = reference.indexOf("?");
if (queryStringPos != -1)
{
final String queryString = reference.substring(queryStringPos + 1);
pageParameters = new PageParameters();
RequestUtils.decodeParameters(queryString, pageParameters);
infoPath = reference.substring(0, queryStringPos);
}
else
{
pageParameters = null;
infoPath = reference;
}
absolute = (infoPath.startsWith("/") || infoPath.startsWith("\\"));
// remove file extension, but remember it
String extension = null;
int pos = infoPath.lastIndexOf(".");
if (pos != -1)
{
extension = infoPath.substring(pos + 1);
infoPath = infoPath.substring(0, pos);
}
String anchor = null;
if (extension != null)
{
pos = extension.indexOf('#');
if (pos != -1)
{
anchor = extension.substring(pos);
extension = extension.substring(0, pos);
}
}
// Anchors without path, e.g. "#link"
if (anchor == null)
{
pos = infoPath.indexOf("#");
if (pos != -1)
{
anchor = infoPath.substring(pos);
infoPath = infoPath.substring(0, pos);
}
}
path = infoPath;
this.extension = extension;
this.anchor = anchor;
}
/**
* Gets the anchor (e.g. #top)
*
* @return anchor
*/
public final String getAnchor()
{
return anchor;
}
/**
* Gets extension.
*
* @return extension
*/
public final String getExtension()
{
return extension;
}
/**
* Gets pageParameters.
*
* @return pageParameters
*/
public final PageParameters getPageParameters()
{
return pageParameters;
}
/**
* Gets path.
*
* @return path
*/
public final String getPath()
{
return path;
}
/**
* Gets reference.
*
* @return reference
*/
public final String getReference()
{
return reference;
}
/**
* Gets absolute.
*
* @return absolute
*/
public final boolean isAbsolute()
{
return absolute;
}
}
/**
* Resolves to anchor/ link components.
*/
private static final class AnchorResolverDelegate extends AbstractAutolinkResolverDelegate
{
/** the attribute to fetch. */
private static final String attribute = "href";
/**
* Set of supported extensions for creating bookmarkable page links. Anything that is not in
* this list will be handled as a resource reference.
*/
private final Set supportedPageExtensions = new HashSet(4);
/**
* Construct.
*/
public AnchorResolverDelegate()
{
// Initialize supported list of file name extension which'll create
// bookmarkable pages
supportedPageExtensions.add("html");
supportedPageExtensions.add("xml");
supportedPageExtensions.add("wml");
supportedPageExtensions.add("svg");
}
/**
* @see org.apache.wicket.markup.resolver.AutoLinkResolver.IAutolinkResolverDelegate#newAutoComponent(org.apache.wicket.MarkupContainer,
* java.lang.String, org.apache.wicket.markup.resolver.AutoLinkResolver.PathInfo)
*/
@SuppressWarnings("unchecked")
public Component newAutoComponent(final MarkupContainer container, final String autoId,
PathInfo pathInfo)
{
if ((pathInfo.extension != null) &&
supportedPageExtensions.contains(pathInfo.extension))
{
// Obviously a href like href="myPkg.MyLabel.html" will do as
// well. Wicket will not throw an exception. It accepts it.
Page page = container.getPage();
final IClassResolver defaultClassResolver = page.getApplication()
.getApplicationSettings()
.getClassResolver();
String className = Packages.absolutePath(page.getClass(), pathInfo.path);
className = Strings.replaceAll(className, "/", ".").toString();
if (className.startsWith("."))
{
className = className.substring(1);
}
try
{
final Class extends Page> clazz = (Class extends Page>)defaultClassResolver.resolveClass(className);
return new AutolinkBookmarkablePageLink(autoId, clazz,
pathInfo.pageParameters, pathInfo.anchor);
}
catch (ClassNotFoundException ex)
{
log.warn("Did not find corresponding java class: " + className);
// fall through
}
// Make sure base markup pages (inheritance) are handled correct
MarkupContainer parentWithContainer = container;
if (container.getParent() != null)
{
parentWithContainer = container.findParentWithAssociatedMarkup();
}
if ((parentWithContainer instanceof Page) && !pathInfo.path.startsWith("/") &&
new MarkupStream(page.getMarkup()).isMergedMarkup())
{
IMarkupFragment containerMarkup = container.getMarkup();
MarkupStream containerMarkupStream = new MarkupStream(containerMarkup);
if (containerMarkupStream.atTag())
{
ComponentTag tag = containerMarkupStream.getTag();
Class extends Page> clazz = (Class extends Page>)tag.getMarkupClass();
if (clazz != null)
{
// Href is relative. Resolve the url given relative to
// the current page
className = Packages.absolutePath(clazz, pathInfo.path);
className = Strings.replaceAll(className, "/", ".").toString();
if (className.startsWith("."))
{
className = className.substring(1);
}
try
{
clazz = (Class extends Page>)defaultClassResolver.resolveClass(className);
return new AutolinkBookmarkablePageLink(autoId, clazz,
pathInfo.getPageParameters(), pathInfo.anchor);
}
catch (ClassNotFoundException ex)
{
log.warn("Did not find corresponding java class: " + className);
// fall through
}
}
}
}
}
else
{
// not a registered type for bookmarkable pages; create a link
// to a resource instead
return newPackageResourceReferenceAutoComponent(container, autoId, pathInfo,
attribute);
}
// fallthrough
return null;
}
}
/**
* Autolink components delegate component resolution to their parent components. Reason:
* autolink tags don't have wicket:id and users wouldn't know where to add the component to.
*
* @author Juergen Donnerstag
*/
private final static class AutolinkExternalLink extends ExternalLink
implements
IComponentResolver
{
private static final long serialVersionUID = 1L;
/**
* Construct
*
* @param id
* @param href
*/
public AutolinkExternalLink(final String id, final String href)
{
super(id, href);
}
/**
* @see org.apache.wicket.markup.resolver.IComponentResolver#resolve(org.apache.wicket.MarkupContainer,
* org.apache.wicket.markup.MarkupStream, org.apache.wicket.markup.ComponentTag)
*/
public Component resolve(MarkupContainer container, MarkupStream markupStream,
ComponentTag tag)
{
return getParent().get(tag.getId());
}
}
/**
* Resolver that returns the proper attribute value from a component tag reflecting a URL
* reference such as src or href.
*/
private static interface ITagReferenceResolver
{
/**
* Gets the reference attribute value of the tag depending on the type of the tag. For
* instance, anchors use the href
attribute but script and image references use
* the src
attribute.
*
* @param tag
* The component tag. Not for modification.
* @return the tag value that constitutes the reference
*/
String getReference(final ComponentTag tag);
}
/**
* Autolink component that points to a {@link ResourceReference}. Autolink component delegate
* component resolution to their parent components. Reason: autolink tags don't have wicket:id
* and users wouldn't know where to add the component to.
*/
private final static class ResourceReferenceAutolink extends WebMarkupContainer
implements
IComponentResolver
{
private static final long serialVersionUID = 1L;
private final String attribute;
/** Resource reference */
private final ResourceReference resourceReference;
private final MarkupContainer parent;
/**
* @param id
* @param clazz
* @param href
* @param attribute
* @param parent
*/
public ResourceReferenceAutolink(final String id, final Class> clazz, final String href,
final String attribute, MarkupContainer parent)
{
super(id);
this.parent = parent;
this.attribute = attribute;
ResourceReference reference = null;
// Check whether it is a valid resource reference
Class> cursor = clazz;
// iterate all parents because the auto linked resource may come from
// inherited markup
while (cursor != null && cursor != Object.class)
{
if (PackageResource.exists(cursor, href, getLocale(), getStyle(), getVariation()))
{
// Create the component implementing the link
reference = new PackageResourceReference(cursor, href, getLocale(), getStyle(),
getVariation());
break;
}
cursor = cursor.getSuperclass();
}
resourceReference = reference;
}
/**
* @see org.apache.wicket.Component#getVariation()
*/
@Override
public String getVariation()
{
if (parent != null)
{
return parent.getVariation();
}
return super.getVariation();
}
/**
* Handles this link's tag.
*
* @param tag
* the component tag
* @see org.apache.wicket.Component#onComponentTag(ComponentTag)
*/
@Override
protected final void onComponentTag(final ComponentTag tag)
{
// Default handling for tag
super.onComponentTag(tag);
// only set the href attribute when the resource exists
if (resourceReference != null)
{
// Set href to link to this link's linkClicked method
ResourceReferenceRequestHandler handler = new ResourceReferenceRequestHandler(
resourceReference);
CharSequence url = getRequestCycle().urlFor(handler);
// generate the href attribute
tag.put(attribute, url);
}
}
/**
* @see org.apache.wicket.markup.resolver.IComponentResolver#resolve(org.apache.wicket.MarkupContainer,
* org.apache.wicket.markup.MarkupStream, org.apache.wicket.markup.ComponentTag)
*/
public Component resolve(MarkupContainer container, MarkupStream markupStream,
ComponentTag tag)
{
return getParent().get(tag.getId());
}
}
/**
* Resolves to {@link ResourceReference} link components. Typically used for header
* contributions like javascript and css files.
*/
private static final class ResourceReferenceResolverDelegate extends
AbstractAutolinkResolverDelegate
{
private final String attribute;
/**
* Construct.
*
* @param attribute
*/
public ResourceReferenceResolverDelegate(final String attribute)
{
this.attribute = attribute;
}
/**
* @see org.apache.wicket.markup.resolver.AutoLinkResolver.IAutolinkResolverDelegate#newAutoComponent(org.apache.wicket.MarkupContainer,
* java.lang.String, org.apache.wicket.markup.resolver.AutoLinkResolver.PathInfo)
*/
public Component newAutoComponent(final MarkupContainer container, final String autoId,
final PathInfo pathInfo)
{
return newPackageResourceReferenceAutoComponent(container, autoId, pathInfo, attribute);
}
}
/**
* Resolver object that returns the proper attribute value from component tags.
*/
private static final class TagReferenceResolver implements ITagReferenceResolver
{
/** the attribute to fetch. */
private final String attribute;
/**
* Construct.
*
* @param attribute
* the attribute to fetch
*/
public TagReferenceResolver(final String attribute)
{
this.attribute = attribute;
}
/**
* Gets the reference attribute value of the tag depending on the type of the tag. For
* instance, anchors use the href
attribute but script and image references use
* the src
attribute.
*
* @param tag
* The component tag. Not for modification.
* @return the tag value that constitutes the reference
*/
public String getReference(final ComponentTag tag)
{
return tag.getAttributes().getString(attribute);
}
}
/**
* If no specific resolver is found, always use the href attribute for references.
*/
private static final TagReferenceResolver DEFAULT_ATTRIBUTE_RESOLVER = new TagReferenceResolver(
"href");
/** Logging */
private static final Logger log = LoggerFactory.getLogger(AutoLinkResolver.class);
private static final long serialVersionUID = 1L;
/**
* Autolink resolver delegates for constructing new autolinks reference keyed on tag name (such
* as <script> or <a>.
*/
private final Map tagNameToAutolinkResolverDelegates = new HashMap();
/**
* Resolver objects that know what attribute to read for getting the reference keyed on tag name
* (such as <script> or <a>.
*/
private final Map tagNameToTagReferenceResolvers = new HashMap();
/**
* Construct.
*/
public AutoLinkResolver()
{
// register tag reference resolvers
TagReferenceResolver hrefTagReferenceResolver = new TagReferenceResolver("href");
TagReferenceResolver srcTagReferenceResolver = new TagReferenceResolver("src");
tagNameToTagReferenceResolvers.put("a", hrefTagReferenceResolver);
tagNameToTagReferenceResolvers.put("link", hrefTagReferenceResolver);
tagNameToTagReferenceResolvers.put("script", srcTagReferenceResolver);
tagNameToTagReferenceResolvers.put("img", srcTagReferenceResolver);
tagNameToTagReferenceResolvers.put("input", srcTagReferenceResolver);
tagNameToTagReferenceResolvers.put("embed", srcTagReferenceResolver);
// register autolink resolver delegates
tagNameToAutolinkResolverDelegates.put("a", new AnchorResolverDelegate());
tagNameToAutolinkResolverDelegates.put("link",
new ResourceReferenceResolverDelegate("href"));
ResourceReferenceResolverDelegate srcResRefResolver = new ResourceReferenceResolverDelegate(
"src");
tagNameToAutolinkResolverDelegates.put("script", srcResRefResolver);
tagNameToAutolinkResolverDelegates.put("img", srcResRefResolver);
tagNameToAutolinkResolverDelegates.put("input", srcResRefResolver);
tagNameToAutolinkResolverDelegates.put("embed", srcResRefResolver);
}
/**
* Register (add or replace) a new resolver with the tagName and attributeName. The resolver
* will be invoked each time an appropriate tag and attribute is found.
*
* @param tagName
* The tag name
* @param attributeName
* The attribute name
* @param resolver
* Implements what to do based on the tag and the attribute
*/
public final void addTagReferenceResolver(final String tagName, final String attributeName,
final IAutolinkResolverDelegate resolver)
{
TagReferenceResolver tagReferenceResolver = new TagReferenceResolver(attributeName);
tagNameToTagReferenceResolvers.put(tagName, tagReferenceResolver);
tagNameToAutolinkResolverDelegates.put(tagName, resolver);
}
/**
* Get the resolver registered for 'tagName'
*
* @param tagName
* The tag's name
* @return The resolver found. Null, if none registered
*/
public final IAutolinkResolverDelegate getAutolinkResolverDelegate(final String tagName)
{
return tagNameToAutolinkResolverDelegates.get(tagName);
}
/**
* @see org.apache.wicket.markup.resolver.IComponentResolver#resolve(org.apache.wicket.MarkupContainer,
* org.apache.wicket.markup.MarkupStream, org.apache.wicket.markup.ComponentTag)
*/
public final Component resolve(final MarkupContainer container,
final MarkupStream markupStream, final ComponentTag tag)
{
// Must be marked as autolink tag
if (tag.isAutolinkEnabled())
{
// Try to find the Page matching the href
// Note: to not use tag.getId() because it will be modified while
// resolving the link and hence the 2nd render will fail.
Component link = resolveAutomaticLink(container, WicketLinkTagHandler.AUTOLINK_ID, tag);
if (log.isDebugEnabled())
{
log.debug("Added autolink " + link);
}
// Tell the container, we resolved the id
return link;
}
// We were not able to resolve the id
return null;
}
/**
* Resolves the given tag's page class and page parameters by parsing the tag component name and
* then searching for a page class at the absolute or relative URL specified by the href
* attribute of the tag.
*
* None html references are treated similar.
*
* @param container
* The container where the link is
* @param id
* the name of the component
* @param tag
* the component tag
* @return A BookmarkablePageLink> to handle the href
*/
private final Component resolveAutomaticLink(final MarkupContainer container, final String id,
final ComponentTag tag)
{
final Page page = container.getPage();
// Make the id (page-)unique
final String autoId = id + Integer.toString(page.getAutoIndex());
// get the tag name, which is something like 'a' or 'script'
final String tagName = tag.getName();
// By setting the component name, the tag becomes a Wicket component
// tag, which must have a associated Component.
if (tag.getId() == null)
{
tag.setAutoComponentTag(true);
}
// tag.setId(autoId);
// get the reference resolver
ITagReferenceResolver referenceResolver = tagNameToTagReferenceResolvers.get(tagName);
if (referenceResolver == null)
{
// fallback on default
referenceResolver = DEFAULT_ATTRIBUTE_RESOLVER;
}
// get the reference, which is typically the value of e.g. a href or src
// attribute
String reference = referenceResolver.getReference(tag);
// create the path info object
PathInfo pathInfo = new PathInfo(reference);
// now get the resolver delegate
IAutolinkResolverDelegate autolinkResolverDelegate = tagNameToAutolinkResolverDelegates.get(tagName);
Component autoComponent = null;
if (autolinkResolverDelegate != null)
{
autoComponent = autolinkResolverDelegate.newAutoComponent(container, autoId, pathInfo);
}
if (autoComponent == null)
{
// resolving didn't have the desired result or there was no delegate
// found; fallback on the default resolving which is a simple
// component that leaves the tag unchanged
autoComponent = new AutolinkExternalLink(autoId, pathInfo.reference);
}
return autoComponent;
}
}