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 org.apache.wicket.Component;
020import org.apache.wicket.MarkupContainer;
021import org.apache.wicket.Page;
022import org.apache.wicket.markup.ComponentTag;
023import org.apache.wicket.markup.MarkupException;
024import org.apache.wicket.markup.MarkupStream;
025import org.apache.wicket.markup.WicketTag;
026import org.apache.wicket.markup.html.TransparentWebMarkupContainer;
027import org.apache.wicket.markup.html.WebMarkupContainer;
028import org.apache.wicket.markup.html.WebPage;
029import org.apache.wicket.markup.html.internal.HtmlHeaderContainer;
030import org.apache.wicket.markup.html.internal.HtmlHeaderItemsContainer;
031import org.apache.wicket.markup.parser.filter.HtmlHeaderSectionHandler;
032import org.apache.wicket.markup.parser.filter.WicketTagIdentifier;
033import org.apache.wicket.util.resource.IResourceStream;
034import org.apache.wicket.util.visit.IVisit;
035import org.apache.wicket.util.visit.IVisitor;
036
037/**
038 * This is a tag resolver which handles <head> and <wicket:head>tags. It must be
039 * registered (with the application) and assumes that a ComponentTag respectively a WicketTag has
040 * already been created (see {@link HtmlHeaderSectionHandler} and {@link WicketTagIdentifier}).
041 * <p>
042 * Provided the current tag is a &lt;head&gt;, a {@link HtmlHeaderContainer} component is created,
043 * (auto) added to the component hierarchy and immediately rendered. Please see the javadoc for
044 * {@link HtmlHeaderContainer} on how it treats the tag.
045 * <p>
046 * In case of &lt;wicket:head&gt; a simple {@link TransparentWebMarkupContainer} handles the tag.
047 * 
048 * @author Juergen Donnerstag
049 */
050public class HtmlHeaderResolver implements IComponentResolver
051{
052        private static final long serialVersionUID = 1L;
053
054        /** */
055        public static final String HEAD = "head";
056        public static final String HEADER_ITEMS = "header-items";
057
058        @Override
059        public Component resolve(final MarkupContainer container, final MarkupStream markupStream,
060                final ComponentTag tag)
061        {
062                final Page page = container.getPage();
063
064                // <head> or <wicket:header-items/> component tags have the id == "_header_"
065                if (tag.getId().equals(HtmlHeaderSectionHandler.HEADER_ID))
066                {
067                        // Create a special header component which will gather additional
068                        // input the <head> from 'contributors'.
069                        return newHtmlHeaderContainer(tag.getId(), tag);
070                }
071                else if ((tag instanceof WicketTag) && ((WicketTag)tag).isHeadTag())
072                {
073                        // If we found <wicket:head> without surrounding <head> on a Page,
074                        // then we have to add wicket:head into a automatically generated
075                        // head first.
076                        if (container instanceof WebPage)
077                        {
078                                HtmlHeaderContainer header = container.visitChildren(new IVisitor<Component, HtmlHeaderContainer>()
079                                {
080                                        @Override
081                                        public void component(final Component component, final IVisit<HtmlHeaderContainer> visit)
082                                        {
083                                                if (component instanceof HtmlHeaderContainer)
084                                                {
085                                                        visit.stop((HtmlHeaderContainer) component);
086                                                } else if (component instanceof TransparentWebMarkupContainer == false)
087                                                {
088                                                        visit.dontGoDeeper();
089                                                }
090                                        }
091                                });
092
093                                // It is <wicket:head>. Because they do not provide any
094                                // additional functionality they are merely a means of surrounding relevant
095                                // markup. Thus we simply create a WebMarkupContainer to handle
096                                // the tag (class WicketHeadContainer).
097                                
098                                if (header == null)
099                                {
100                                        // Create a special header component which will gather
101                                        // additional input the <head> from 'contributors'.
102                                        header = newHtmlHeaderContainer(tag.getId(), tag);
103                                        header.add(new WicketHeadContainer());
104                                        return header;
105                                }
106
107                                WicketHeadContainer wicketHeadContainer = 
108                                        header.visitChildren(new FindWicketHeadContainer());
109                                
110                                //We just need one WicketHeadContainer, no matter how 
111                                //many <wicket:head> we have.
112                                if (wicketHeadContainer == null)
113                                {
114                                        wicketHeadContainer = new WicketHeadContainer();
115                                        header.add(wicketHeadContainer);
116                                }
117                                
118                                return wicketHeadContainer;
119                        }
120                        else if (container instanceof HtmlHeaderContainer)
121                        {
122                                // It is <wicket:head>. Because they do not provide any
123                                // additional functionality there are merely a means of surrounding
124                                // relevant markup. Thus we simply create a WebMarkupContainer to handle
125                                // the tag.
126                                WebMarkupContainer header = new WicketHeadContainer();
127
128                                return header;
129                        }
130
131                        final String pageClassName = (page != null) ? page.getClass().getName() : "unknown";
132                        final IResourceStream stream = markupStream.getResource();
133                        final String streamName = (stream != null) ? stream.toString() : "unknown";
134
135                        throw new MarkupException(
136                                "Mis-placed <wicket:head>. <wicket:head> must be outside of <wicket:panel>, <wicket:border>, " +
137                                                "and <wicket:extend>. Error occurred while rendering page: " +
138                                        pageClassName + " using markup stream: " + streamName);
139                }
140
141                // We were not able to handle the tag
142                return null;
143        }
144
145        /**
146         * Return a new HtmlHeaderContainer
147         *
148         * @param id
149         * @return HtmlHeaderContainer
150         */
151        protected HtmlHeaderContainer newHtmlHeaderContainer(String id, ComponentTag tag)
152        {
153                HtmlHeaderContainer htmlHeaderContainer;
154                if (HtmlHeaderResolver.HEADER_ITEMS.equalsIgnoreCase(tag.getName()))
155                {
156                        htmlHeaderContainer = new HtmlHeaderItemsContainer(id);
157                }
158                else
159                {
160                        htmlHeaderContainer = new HtmlHeaderContainer(id);
161                }
162
163                return htmlHeaderContainer;
164        }
165
166        /**
167         * A component for &lt;wicket:head&gt; elements
168         */
169        private static class WicketHeadContainer extends WebMarkupContainer
170        {
171                private static final long serialVersionUID = 1L;
172
173                /**
174                 * Constructor.
175                 */
176                public WicketHeadContainer()
177                {
178                        super(HtmlHeaderSectionHandler.HEADER_ID);
179
180                        setRenderBodyOnly(true);
181                }
182        }
183        
184        /**
185         * Visitor to find children of type {@link WicketHeadContainer}}
186         */
187        private static class FindWicketHeadContainer implements 
188                        IVisitor<Component, WicketHeadContainer>
189        {
190                @Override
191                public void component(Component component, IVisit<WicketHeadContainer> visit)
192                {
193                        if (component instanceof WicketHeadContainer)
194                        {
195                                WicketHeadContainer result = (WicketHeadContainer) component;
196                                visit.stop(result);
197                        }
198                }       
199        }
200}