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 <head>, 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 <wicket:head> 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 <wicket:head> 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}