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