001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.markup.resolver;
018
019import java.util.HashMap;
020import java.util.HashSet;
021import java.util.Map;
022import java.util.Set;
023
024import org.apache.wicket.Component;
025import org.apache.wicket.MarkupContainer;
026import org.apache.wicket.Page;
027import org.apache.wicket.application.IClassResolver;
028import org.apache.wicket.markup.ComponentTag;
029import org.apache.wicket.markup.IMarkupFragment;
030import org.apache.wicket.markup.MarkupStream;
031import org.apache.wicket.markup.html.TransparentWebMarkupContainer;
032import org.apache.wicket.markup.html.WebMarkupContainer;
033import org.apache.wicket.markup.html.link.BookmarkablePageLink;
034import org.apache.wicket.markup.html.link.DisabledAttributeLinkBehavior;
035import org.apache.wicket.protocol.http.RequestUtils;
036import org.apache.wicket.request.handler.resource.ResourceReferenceRequestHandler;
037import org.apache.wicket.request.mapper.parameter.PageParameters;
038import org.apache.wicket.request.resource.PackageResource;
039import org.apache.wicket.request.resource.PackageResourceReference;
040import org.apache.wicket.request.resource.ResourceReference;
041import org.apache.wicket.util.lang.Packages;
042import org.apache.wicket.util.string.Strings;
043import org.slf4j.Logger;
044import org.slf4j.LoggerFactory;
045
046/**
047 * The AutoLinkResolver is responsible to handle automatic link resolution. Tags are marked
048 * "autolink" by the MarkupParser for all tags with href attribute, such as anchor and link tags
049 * with no explicit wicket id. E.g. <a href="Home.html">
050 * <p>
051 * If href points to a *.html file, a BookmarkablePageLink will automatically be created, except
052 * for absolute paths, where an ExternalLink is created.
053 * <p>
054 * If href points to a *.html file, it resolves the given URL by searching for a page class, either
055 * relative or absolute, specified by the href attribute of the tag. If relative the href URL must
056 * be relative to the package containing the associated page. An exception is thrown if no Page
057 * class was found.
058 * <p>
059 * If href is no *.html file a static reference to the resource is created.
060 * 
061 * @see org.apache.wicket.markup.parser.filter.WicketLinkTagHandler
062 * 
063 * @author Juergen Donnerstag
064 * @author Eelco Hillenius
065 */
066public final class AutoLinkResolver implements IComponentResolver
067{
068        /**
069         * Abstract implementation that has a helper method for creating a resource reference.
070         */
071        public static abstract class AbstractAutolinkResolverDelegate
072                implements
073                        IAutolinkResolverDelegate
074        {
075                /**
076                 * Creates a new auto component that references a package resource.
077                 *
078                 * @param autoId
079                 *            the automatically generated id for the auto component
080                 * @param pathInfo
081                 *            the path info object that contains information about the link reference
082                 * @param attribute
083                 *            the attribute to replace the value of
084                 * @return a new auto component or null if the path was absolute
085                 */
086                protected final Component newPackageResourceReferenceAutoComponent(
087                        final String autoId, final PathInfo pathInfo,
088                        final String attribute)
089                {
090                        final MarkupContainer container = pathInfo.getContainer();
091
092                        if (!pathInfo.absolute && (pathInfo.path != null) && (pathInfo.path.length() > 0))
093                        {
094                                // Href is relative. Create a resource reference pointing at this file
095
096                                // <wicket:head> components are handled differently. We can
097                                // not use the container, because it is the container the
098                                // header has been added to (e.g. the Page). What we need
099                                // however, is the component (e.g. a Panel) which
100                                // contributed it.
101                                MarkupStream markupStream = pathInfo.getMarkupStream();
102                                Class<? extends Component> clazz = markupStream.getContainerClass();
103
104                                // However if the markup stream is a merged markup stream (inheritance), than we
105                                // need the class of the markup file which contained the tag.
106                                if ((markupStream.get() instanceof ComponentTag) &&
107                                        (markupStream.getTag().getMarkupClass() != null))
108                                {
109                                        clazz = markupStream.getTag().getMarkupClass();
110                                }
111
112                                // Create the component implementing the link
113                                ResourceReferenceAutolink autoLink = new ResourceReferenceAutolink(autoId, clazz,
114                                        pathInfo.reference, attribute, container);
115                                if (autoLink.resourceReference != null)
116                                {
117                                        // if the resource reference is null, it means that it the
118                                        // reference was not found as a package resource
119                                        return autoLink;
120                                }
121                        }
122                        // else we can't have absolute resource references, at least not at
123                        // this time
124
125                        // fall back on default processing
126                        return null;
127                }
128        }
129
130        /**
131         * Autolink components delegate component resolution to their parent components. Reason:
132         * autolink tags don't have wicket:id and users wouldn't know where to add the component to.
133         * 
134         * @author Juergen Donnerstag
135         * @param <T>
136         *            type of model object
137         */
138        public final static class AutolinkBookmarkablePageLink<T> extends BookmarkablePageLink<T>
139                implements
140                        IComponentResolver
141        {
142                private static final long serialVersionUID = 1L;
143
144                private final String anchor;
145
146                /**
147                 * When using &lt;wicket:link&gt; to let Wicket lookup for pages and create the related links,
148                 * it's not possible to change the "setAutoEnable" property, which defaults to true. This
149                 * affects the prototype because, sometimes designers _want_ links to be enabled.
150                 */
151                public static boolean autoEnable = true;
152
153                /**
154                 * Construct
155                 * 
156                 * @param <C>
157                 * 
158                 * @see BookmarkablePageLink#BookmarkablePageLink(String, Class, PageParameters)
159                 * 
160                 * @param id
161                 * @param pageClass
162                 * @param parameters
163                 * @param anchor
164                 */
165                public <C extends Page> AutolinkBookmarkablePageLink(final String id,
166                        final Class<C> pageClass, final PageParameters parameters, final String anchor)
167                {
168                        super(id, pageClass, parameters);
169                        this.anchor = anchor;
170                        setAutoEnable(autoEnable);
171                        add(new DisabledAttributeLinkBehavior());
172                }
173
174                /**
175                 * 
176                 * @see org.apache.wicket.markup.html.link.BookmarkablePageLink#getURL()
177                 */
178                @Override
179                protected CharSequence getURL()
180                {
181                        CharSequence url = super.getURL();
182                        if (anchor != null)
183                        {
184                                url = url + anchor;
185                        }
186
187                        return url;
188                }
189
190                /**
191                 * @see org.apache.wicket.markup.resolver.IComponentResolver#resolve(org.apache.wicket.MarkupContainer,
192                 *      org.apache.wicket.markup.MarkupStream, org.apache.wicket.markup.ComponentTag)
193                 */
194                @Override
195                public Component resolve(final MarkupContainer container, final MarkupStream markupStream,
196                        ComponentTag tag)
197                {
198                        return getParent().get(tag.getId());
199                }
200        }
201
202        /**
203         * Interface to delegate the actual resolving of auto components to.
204         */
205        public interface IAutolinkResolverDelegate
206        {
207                /**
208                 * Returns a new auto component based on the pathInfo object. The auto component must have
209                 * the autoId assigned as it's id. Should return null in case the component could not be
210                 * created as expected and the default resolving should take place.
211                 * 
212                 * @param autoId
213                 *            the automatically generated id for the auto component
214                 * @param pathInfo
215                 *            the path info object that contains information about the link reference
216                 * @return a new auto component or null in case this method couldn't resolve to a proper
217                 *         auto component
218                 */
219                Component newAutoComponent(final String autoId, final PathInfo pathInfo);
220        }
221
222        /**
223         * Encapsulates different aspects of a path. For instance, the path
224         * <code>org.apache.wicket.markup.html.tree.Tree/tree.css</code> has extension <code>css</code>,
225         * is relative (absolute == true) and has no page parameters.
226         */
227        public static final class PathInfo
228        {
229                /** whether the reference is absolute. */
230                private final boolean absolute;
231
232                /** An optional anchor like #top */
233                private final String anchor;
234
235                /** The extension if any. */
236                private final String extension;
237
238                /** The optional page parameters. */
239                private final PageParameters pageParameters;
240
241                /** The path excluding any parameters. */
242                private final String path;
243
244                /** The original reference (e.g the full value of a href attribute). */
245                private final String reference;
246
247                /** The container for this path */
248                private final MarkupContainer container;
249
250                /** Parent markup stream */
251                private final MarkupStream markupStream;
252
253                /**
254                 * Construct.
255                 * 
256                 * @param reference
257                 *            the original reference (e.g the full value of a href attribute)
258                 */
259                public PathInfo(final String reference, MarkupContainer container, MarkupStream markupStream)
260                {
261                        this.reference = reference;
262                        this.container = container;
263                        this.markupStream = markupStream;
264                        // If href contains URL query parameters ..
265                        String infoPath;
266                        // get the query string
267                        int queryStringPos = reference.indexOf("?");
268                        if (queryStringPos != -1)
269                        {
270                                final String queryString = reference.substring(queryStringPos + 1);
271                                pageParameters = new PageParameters();
272                                RequestUtils.decodeParameters(queryString, pageParameters);
273                                infoPath = reference.substring(0, queryStringPos);
274                        }
275                        else
276                        {
277                                pageParameters = null;
278                                infoPath = reference;
279                        }
280
281                        absolute = (infoPath.startsWith("/") || infoPath.startsWith("\\"));
282
283                        // remove file extension, but remember it
284                        String extension = null;
285                        int pos = infoPath.lastIndexOf(".");
286                        if (pos != -1)
287                        {
288                                extension = infoPath.substring(pos + 1);
289                                infoPath = infoPath.substring(0, pos);
290                        }
291
292                        String anchor = null;
293                        if (extension != null)
294                        {
295                                pos = extension.indexOf('#');
296                                if (pos != -1)
297                                {
298                                        anchor = extension.substring(pos);
299                                        extension = extension.substring(0, pos);
300                                }
301                        }
302
303                        // Anchors without path, e.g. "#link"
304                        if (anchor == null)
305                        {
306                                pos = infoPath.indexOf("#");
307                                if (pos != -1)
308                                {
309                                        anchor = infoPath.substring(pos);
310                                        infoPath = infoPath.substring(0, pos);
311                                }
312                        }
313
314                        path = infoPath;
315                        this.extension = extension;
316                        this.anchor = anchor;
317                }
318
319                /**
320                 * Gets the anchor (e.g. #top)
321                 * 
322                 * @return anchor
323                 */
324                public final String getAnchor()
325                {
326                        return anchor;
327                }
328
329                /**
330                 * Gets extension.
331                 * 
332                 * @return extension
333                 */
334                public final String getExtension()
335                {
336                        return extension;
337                }
338
339                /**
340                 * Gets pageParameters.
341                 * 
342                 * @return pageParameters
343                 */
344                public final PageParameters getPageParameters()
345                {
346                        return pageParameters;
347                }
348
349                /**
350                 * Gets path.
351                 * 
352                 * @return path
353                 */
354                public final String getPath()
355                {
356                        return path;
357                }
358
359                /**
360                 * Gets reference.
361                 * 
362                 * @return reference
363                 */
364                public final String getReference()
365                {
366                        return reference;
367                }
368
369                /**
370                 * Gets absolute.
371                 * 
372                 * @return absolute
373                 */
374                public final boolean isAbsolute()
375                {
376                        return absolute;
377                }
378
379                /**
380                 * Gets container.
381                 *
382                 * @return container
383                 */
384                public MarkupContainer getContainer()
385                {
386                        return container;
387                }
388
389                /**
390                 * Gets markup stream
391                 *
392                 * @return markup stream
393                 */
394                public MarkupStream getMarkupStream()
395                {
396                        return markupStream;
397                }
398        }
399
400        /**
401         * Resolves to anchor/ link components.
402         */
403        private static final class AnchorResolverDelegate extends AbstractAutolinkResolverDelegate
404        {
405                /** the attribute to fetch. */
406                private static final String attribute = "href";
407
408                /**
409                 * Set of supported extensions for creating bookmarkable page links. Anything that is not in
410                 * this list will be handled as a resource reference.
411                 */
412                private final Set<String> supportedPageExtensions = new HashSet<>(4);
413
414                /**
415                 * Construct.
416                 */
417                public AnchorResolverDelegate()
418                {
419                        // Initialize supported list of file name extension which'll create
420                        // bookmarkable pages
421                        supportedPageExtensions.add("html");
422                        supportedPageExtensions.add("xml");
423                        supportedPageExtensions.add("wml");
424                        supportedPageExtensions.add("svg");
425                }
426
427                /**
428                 * @see org.apache.wicket.markup.resolver.AutoLinkResolver.IAutolinkResolverDelegate#newAutoComponent(java.lang.String,
429                 * org.apache.wicket.markup.resolver.AutoLinkResolver.PathInfo)
430                 */
431                @Override
432                @SuppressWarnings("unchecked")
433                public Component newAutoComponent(final String autoId, PathInfo pathInfo)
434                {
435                        final MarkupContainer container = pathInfo.getContainer();
436
437                        if ((pathInfo.extension != null) &&
438                                supportedPageExtensions.contains(pathInfo.extension))
439                        {
440                                // Obviously a href like href="myPkg.MyLabel.html" will do as
441                                // well. Wicket will not throw an exception. It accepts it.
442
443                                Page page = container.getPage();
444                                final IClassResolver defaultClassResolver = page.getApplication()
445                                        .getApplicationSettings()
446                                        .getClassResolver();
447                                String className = Packages.absolutePath(page.getClass(), pathInfo.path);
448                                className = Strings.replaceAll(className, "/", ".").toString();
449                                if (className.startsWith("."))
450                                {
451                                        className = className.substring(1);
452                                }
453
454                                try
455                                {
456                                        final Class<? extends Page> clazz = (Class<? extends Page>)defaultClassResolver.resolveClass(className);
457                                        return new AutolinkBookmarkablePageLink<Void>(autoId, clazz,
458                                                pathInfo.pageParameters, pathInfo.anchor);
459                                }
460                                catch (ClassNotFoundException ex)
461                                {
462                                        log.warn("Did not find corresponding java class: " + className);
463                                        // fall through
464                                }
465
466                                // Make sure base markup pages (inheritance) are handled correct
467                                MarkupContainer parentWithContainer = container;
468                                if (container.getParent() != null)
469                                {
470                                        parentWithContainer = container.findParentWithAssociatedMarkup();
471                                }
472                                if ((parentWithContainer instanceof Page) && !pathInfo.path.startsWith("/") &&
473                                        new MarkupStream(page.getMarkup()).isMergedMarkup())
474                                {
475                                        IMarkupFragment containerMarkup = container.getMarkup();
476                                        MarkupStream containerMarkupStream = new MarkupStream(containerMarkup);
477                                        if (containerMarkupStream.atTag())
478                                        {
479                                                ComponentTag tag = containerMarkupStream.getTag();
480                                                Class<? extends Page> clazz = (Class<? extends Page>)tag.getMarkupClass();
481                                                if (clazz != null)
482                                                {
483                                                        // Href is relative. Resolve the url given relative to
484                                                        // the current page
485                                                        className = Packages.absolutePath(clazz, pathInfo.path);
486                                                        className = Strings.replaceAll(className, "/", ".").toString();
487                                                        if (className.startsWith("."))
488                                                        {
489                                                                className = className.substring(1);
490                                                        }
491
492                                                        try
493                                                        {
494                                                                clazz = (Class<? extends Page>)defaultClassResolver.resolveClass(className);
495                                                                return new AutolinkBookmarkablePageLink<Void>(autoId, clazz,
496                                                                        pathInfo.getPageParameters(), pathInfo.anchor);
497                                                        }
498                                                        catch (ClassNotFoundException ex)
499                                                        {
500                                                                log.warn("Did not find corresponding java class: " + className);
501                                                                // fall through
502                                                        }
503                                                }
504                                        }
505                                }
506                        }
507                        else
508                        {
509                                // not a registered type for bookmarkable pages; create a link
510                                // to a resource instead
511                                return newPackageResourceReferenceAutoComponent(autoId, pathInfo, attribute);
512                        }
513
514                        // fallthrough
515                        return null;
516                }
517        }
518
519        /**
520         * Resolver that returns the proper attribute value from a component tag reflecting a URL
521         * reference such as src or href.
522         */
523        private interface ITagReferenceResolver
524        {
525                /**
526                 * Gets the reference attribute value of the tag depending on the type of the tag. For
527                 * instance, anchors use the <code>href</code> attribute but script and image references use
528                 * the <code>src</code> attribute.
529                 * 
530                 * @param tag
531                 *            The component tag. Not for modification.
532                 * @return the tag value that constitutes the reference
533                 */
534                String getReference(final ComponentTag tag);
535        }
536
537        /**
538         * Autolink component that points to a {@link ResourceReference}. Autolink component delegate
539         * component resolution to their parent components. Reason: autolink tags don't have wicket:id
540         * and users wouldn't know where to add the component to.
541         */
542        private final static class ResourceReferenceAutolink extends WebMarkupContainer
543                implements
544                        IComponentResolver
545        {
546                private static final long serialVersionUID = 1L;
547
548                private final String attribute;
549
550                /** Resource reference */
551                private final ResourceReference resourceReference;
552
553                private final MarkupContainer parent;
554
555                /**
556                 * @param id
557                 * @param clazz
558                 * @param href
559                 * @param attribute
560                 * @param parent
561                 */
562                public ResourceReferenceAutolink(final String id, final Class<?> clazz, final String href,
563                        final String attribute, MarkupContainer parent)
564                {
565                        super(id);
566
567                        this.parent = parent;
568                        this.attribute = attribute;
569                        // Check whether it is a valid resource reference
570                        if (PackageResource.exists(clazz, href, getLocale(), getStyle(), getVariation()))
571                        {
572                                // Create the component implementing the link
573                                resourceReference = new PackageResourceReference(clazz, href, null, null, null);
574                                }
575                                else
576                                {
577                                        // The resource does not exist. Set to null and ignore when
578                                        // rendering.
579                                        resourceReference = null;
580                                }
581                }
582
583                /**
584                 * @see org.apache.wicket.Component#getVariation()
585                 */
586                @Override
587                public String getVariation()
588                {
589                        if (parent != null)
590                        {
591                                return parent.getVariation();
592                        }
593
594                        return super.getVariation();
595                }
596
597
598                /**
599                 * Handles this link's tag.
600                 * 
601                 * @param tag
602                 *            the component tag
603                 * @see org.apache.wicket.Component#onComponentTag(ComponentTag)
604                 */
605                @Override
606                protected final void onComponentTag(final ComponentTag tag)
607                {
608                        // Default handling for tag
609                        super.onComponentTag(tag);
610
611                        // only set the href attribute when the resource exists
612                        if (resourceReference != null)
613                        {
614                                // Set href to link to this link's linkClicked method
615
616                                ResourceReferenceRequestHandler handler = new ResourceReferenceRequestHandler(
617                                        resourceReference);
618                                CharSequence url = getRequestCycle().urlFor(handler);
619
620                                // generate the href attribute
621                                tag.put(attribute, url);
622                        }
623                }
624
625                /**
626                 * @see org.apache.wicket.markup.resolver.IComponentResolver#resolve(org.apache.wicket.MarkupContainer,
627                 *      org.apache.wicket.markup.MarkupStream, org.apache.wicket.markup.ComponentTag)
628                 */
629                @Override
630                public Component resolve(MarkupContainer container, MarkupStream markupStream,
631                        ComponentTag tag)
632                {
633                        return getParent().get(tag.getId());
634                }
635        }
636
637        /**
638         * Resolves to {@link ResourceReference} link components. Typically used for header
639         * contributions like javascript and css files.
640         */
641        private static final class ResourceReferenceResolverDelegate extends
642                AbstractAutolinkResolverDelegate
643        {
644                private final String attribute;
645
646                /**
647                 * Construct.
648                 * 
649                 * @param attribute
650                 */
651                public ResourceReferenceResolverDelegate(final String attribute)
652                {
653                        this.attribute = attribute;
654                }
655
656                /**
657                 * @see org.apache.wicket.markup.resolver.AutoLinkResolver.IAutolinkResolverDelegate#newAutoComponent(java.lang.String,
658                 * org.apache.wicket.markup.resolver.AutoLinkResolver.PathInfo)
659                 */
660                @Override
661                public Component newAutoComponent(final String autoId, final PathInfo pathInfo)
662                {
663                        return newPackageResourceReferenceAutoComponent(autoId, pathInfo, attribute);
664                }
665        }
666
667        /**
668         * Resolver object that returns the proper attribute value from component tags.
669         */
670        private static final class TagReferenceResolver implements ITagReferenceResolver
671        {
672                /** the attribute to fetch. */
673                private final String attribute;
674
675                /**
676                 * Construct.
677                 * 
678                 * @param attribute
679                 *            the attribute to fetch
680                 */
681                public TagReferenceResolver(final String attribute)
682                {
683                        this.attribute = attribute;
684                }
685
686                /**
687                 * Gets the reference attribute value of the tag depending on the type of the tag. For
688                 * instance, anchors use the <code>href</code> attribute but script and image references use
689                 * the <code>src</code> attribute.
690                 * 
691                 * @param tag
692                 *            The component tag. Not for modification.
693                 * @return the tag value that constitutes the reference
694                 */
695                @Override
696                public String getReference(final ComponentTag tag)
697                {
698                        return tag.getAttributes().getString(attribute);
699                }
700        }
701
702        /**
703         * If no specific resolver is found, always use the href attribute for references.
704         */
705        private static final TagReferenceResolver DEFAULT_ATTRIBUTE_RESOLVER = new TagReferenceResolver(
706                "href");
707
708        /** Logging */
709        private static final Logger log = LoggerFactory.getLogger(AutoLinkResolver.class);
710
711        private static final long serialVersionUID = 1L;
712
713        /**
714         * Autolink resolver delegates for constructing new autolinks reference keyed on tag name (such
715         * as &lt;script&gt; or &lt;a&gt;.
716         */
717        private final Map<String, IAutolinkResolverDelegate> tagNameToAutolinkResolverDelegates = new HashMap<>();
718
719        /**
720         * Resolver objects that know what attribute to read for getting the reference keyed on tag name
721         * (such as &lt;script&gt; or &lt;a&gt;.
722         */
723        private final Map<String, ITagReferenceResolver> tagNameToTagReferenceResolvers = new HashMap<>();
724
725        /**
726         * Construct.
727         */
728        public AutoLinkResolver()
729        {
730                // register tag reference resolvers
731                TagReferenceResolver hrefTagReferenceResolver = new TagReferenceResolver("href");
732                TagReferenceResolver srcTagReferenceResolver = new TagReferenceResolver("src");
733                tagNameToTagReferenceResolvers.put("a", hrefTagReferenceResolver);
734                tagNameToTagReferenceResolvers.put("link", hrefTagReferenceResolver);
735                tagNameToTagReferenceResolvers.put("script", srcTagReferenceResolver);
736                tagNameToTagReferenceResolvers.put("img", srcTagReferenceResolver);
737                tagNameToTagReferenceResolvers.put("input", srcTagReferenceResolver);
738                tagNameToTagReferenceResolvers.put("embed", srcTagReferenceResolver);
739
740                // register autolink resolver delegates
741                tagNameToAutolinkResolverDelegates.put("a", new AnchorResolverDelegate());
742                tagNameToAutolinkResolverDelegates.put("link",
743                        new ResourceReferenceResolverDelegate("href"));
744                ResourceReferenceResolverDelegate srcResRefResolver = new ResourceReferenceResolverDelegate(
745                        "src");
746                tagNameToAutolinkResolverDelegates.put("script", srcResRefResolver);
747                tagNameToAutolinkResolverDelegates.put("img", srcResRefResolver);
748                tagNameToAutolinkResolverDelegates.put("input", srcResRefResolver);
749                tagNameToAutolinkResolverDelegates.put("embed", srcResRefResolver);
750        }
751
752        /**
753         * Register (add or replace) a new resolver with the tagName and attributeName. The resolver
754         * will be invoked each time an appropriate tag and attribute is found.
755         * 
756         * @param tagName
757         *            The tag name
758         * @param attributeName
759         *            The attribute name
760         * @param resolver
761         *            Implements what to do based on the tag and the attribute
762         */
763        public final void addTagReferenceResolver(final String tagName, final String attributeName,
764                final IAutolinkResolverDelegate resolver)
765        {
766                TagReferenceResolver tagReferenceResolver = new TagReferenceResolver(attributeName);
767                tagNameToTagReferenceResolvers.put(tagName, tagReferenceResolver);
768
769                tagNameToAutolinkResolverDelegates.put(tagName, resolver);
770        }
771
772        /**
773         * Get the resolver registered for 'tagName'
774         * 
775         * @param tagName
776         *            The tag's name
777         * @return The resolver found. Null, if none registered
778         */
779        public final IAutolinkResolverDelegate getAutolinkResolverDelegate(final String tagName)
780        {
781                return tagNameToAutolinkResolverDelegates.get(tagName);
782        }
783
784        /**
785         * @see org.apache.wicket.markup.resolver.IComponentResolver#resolve(org.apache.wicket.MarkupContainer,
786         *      org.apache.wicket.markup.MarkupStream, org.apache.wicket.markup.ComponentTag)
787         */
788        @Override
789        public final Component resolve(final MarkupContainer container,
790                final MarkupStream markupStream, final ComponentTag tag)
791        {
792                // Must be marked as autolink tag
793                if (tag.isAutolinkEnabled())
794                {
795                        // get the reference resolver
796                        ITagReferenceResolver referenceResolver = tagNameToTagReferenceResolvers.get(tag.getName());
797                        if (referenceResolver == null)
798                        {
799                                // fallback on default
800                                referenceResolver = DEFAULT_ATTRIBUTE_RESOLVER;
801                        }
802
803                        // get the reference, which is typically the value of e.g. a href or src
804                        // attribute
805                        String reference = referenceResolver.getReference(tag);
806
807                        // create the path info object
808                        PathInfo pathInfo = new PathInfo(reference, container, markupStream);
809
810                        // Try to find the Page matching the href
811                        // Note: to not use tag.getId() because it will be modified while
812                        // resolving the link and hence the 2nd render will fail.
813                        Component link = resolveAutomaticLink(pathInfo, tag);
814
815                        if (log.isDebugEnabled())
816                        {
817                                log.debug("Added autolink " + link);
818                        }
819
820                        // Tell the container, we resolved the id
821                        return link;
822                }
823
824                // We were not able to resolve the id
825                return null;
826        }
827
828        /**
829         * Resolves the given tag's page class and page parameters by parsing the tag component name and
830         * then searching for a page class at the absolute or relative URL specified by the href
831         * attribute of the tag.
832         * <p>
833         * None html references are treated similar.
834         * 
835         * @param pathInfo
836         *            The container where the link is
837         * @param tag
838         *            the component tag
839         * @return A BookmarkablePageLink<?> to handle the href
840         */
841        private Component resolveAutomaticLink(final PathInfo pathInfo, final ComponentTag tag)
842        {
843                final String componentId = tag.getId();
844
845                // get the tag name, which is something like 'a' or 'script'
846                final String tagName = tag.getName();
847
848                // By setting the component name, the tag becomes a Wicket component
849                // tag, which must have a associated Component.
850                if (tag.getId() == null)
851                {
852                        tag.setAutoComponentTag(true);
853                }
854
855                // now get the resolver delegate
856                IAutolinkResolverDelegate autolinkResolverDelegate = tagNameToAutolinkResolverDelegates.get(tagName);
857                Component autoComponent = null;
858                if (autolinkResolverDelegate != null)
859                {
860                        autoComponent = autolinkResolverDelegate.newAutoComponent(componentId, pathInfo);
861                }
862
863                if (autoComponent == null)
864                {
865                        // resolving didn't have the desired result or there was no delegate
866                        // found; fallback on the default resolving which is a simple
867                        // component that leaves the tag unchanged
868                        autoComponent = new TransparentWebMarkupContainer(componentId);
869                }
870
871                return autoComponent;
872        }
873}