All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.wicket.markup.resolver.AutoLinkResolver Maven / Gradle / Ivy

Go to download

Pax Wicket Service is an OSGi extension of the Wicket framework, allowing for dynamic loading and unloading of Wicket components and pageSources.

There is a newer version: 5.0.0
Show newest version
/*
 * 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.PageParameters;
import org.apache.wicket.ResourceReference;
import org.apache.wicket.application.IClassResolver;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.html.PackageResource;
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.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. Class clazz = container.getMarkupStream().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 (container.getMarkupStream().getTag().getMarkupClass() != null) { clazz = container.getMarkupStream().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 { 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.MarkupContainer#isTransparentResolver() */ @Override public boolean isTransparentResolver() { return true; } /** * * @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; } } /** * 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 clazz = (Class)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("/") && page.getMarkupStream().isMergedMarkup()) { Class clazz = (Class)container.getMarkupStream() .getTag() .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)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 { 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.MarkupContainer#isTransparentResolver() */ @Override public boolean isTransparentResolver() { return true; } } /** * 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 { private static final long serialVersionUID = 1L; private final String attribute; /** Resource reference */ private final ResourceReference resourceReference; /** * Preserve a reference to the the parent container so that the variation could be resolved */ 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, final MarkupContainer parent) { super(id); this.attribute = attribute; this.parent = parent; // Check whether it is a valid resource reference if (PackageResource.exists(clazz, href, getLocale(), getStyle())) { // Create the component implementing the link resourceReference = new ResourceReference(clazz, href, getLocale(), getStyle()); } else { // The resource does not exist. Set to null and ignore when // rendering. resourceReference = null; } } /** * @see org.apache.wicket.Component#getVariation() */ @Override public String getVariation() { if (parent != null) { return parent.getVariation(); } return super.getVariation(); } /** * @see org.apache.wicket.MarkupContainer#isTransparentResolver() */ @Override public boolean isTransparentResolver() { return true; } /** * 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 CharSequence url = getRequestCycle().urlFor(resourceReference); // generate the href attribute tag.put(attribute, Strings.replaceAll(url, "&", "&")); } } } /** * 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); } /** * Automatically creates a BookmarkablePageLink component. * * @see org.apache.wicket.markup.resolver.IComponentResolver#resolve(MarkupContainer, * MarkupStream, ComponentTag) * * @param markupStream * The current markupStream * @param tag * The current component tag while parsing the markup * @param container * The container parsing its markup * @return true, if componentId was handle by the resolver. False, otherwise */ public final boolean 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. final Component link = resolveAutomaticLink(container, WicketLinkTagHandler.AUTOLINK_ID, tag); // Add the link to the container container.autoAdd(link, markupStream); if (log.isDebugEnabled()) { log.debug("Added autolink " + link); } // Tell the container, we resolved the id return true; } // We were not able to resolve the id return false; } /** * 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.setId(autoId); tag.setAutoComponentTag(true); } // 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; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy