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.html.border; 018 019import java.io.IOException; 020import java.util.Locale; 021 022import org.apache.wicket.Application; 023import org.apache.wicket.Component; 024import org.apache.wicket.MarkupContainer; 025import org.apache.wicket.WicketRuntimeException; 026import org.apache.wicket.behavior.Behavior; 027import org.apache.wicket.core.util.resource.locator.IResourceStreamLocator; 028import org.apache.wicket.markup.ContainerInfo; 029import org.apache.wicket.markup.IMarkupFragment; 030import org.apache.wicket.markup.MarkupElement; 031import org.apache.wicket.markup.MarkupFactory; 032import org.apache.wicket.markup.MarkupResourceStream; 033import org.apache.wicket.markup.MarkupStream; 034import org.apache.wicket.markup.MarkupType; 035import org.apache.wicket.markup.WicketTag; 036import org.apache.wicket.request.Response; 037import org.apache.wicket.util.resource.IResourceStream; 038 039/** 040 * This is a behavior implementation that can be used if you have markup that should be around a 041 * component. It works just like {@link Border} so you have to have a <wicket:border>HTML 042 * before <wicket:body/> HTML after </wicket:border> in the html of your subclass. But different than 043 * Border you can not add components to the Border markup, only to the BorderBody. 044 * 045 * @author jcompagner 046 */ 047public class BorderBehavior extends Behavior 048{ 049 private static final long serialVersionUID = 1L; 050 051 // markup stream associated with this border. bonus of keeping a reference 052 // is that when renderAfter starts the stream will be very close to its 053 // needed position because renderBefore has executed 054 private transient MarkupStream markupStream; 055 056 @Override 057 public void beforeRender(final Component component) 058 { 059 final MarkupStream stream = getMarkupStream(component); 060 final Response response = component.getResponse(); 061 stream.setCurrentIndex(0); 062 063 boolean insideBorderMarkup = false; 064 while (stream.isCurrentIndexInsideTheStream()) 065 { 066 MarkupElement elem = stream.get(); 067 stream.next(); 068 if (elem instanceof WicketTag) 069 { 070 WicketTag wTag = (WicketTag)elem; 071 if (!insideBorderMarkup) 072 { 073 if (wTag.isBorderTag() && wTag.isOpen()) 074 { 075 insideBorderMarkup = true; 076 continue; 077 } 078 else 079 { 080 throw new WicketRuntimeException( 081 "Unexpected tag encountered in markup of component border " + 082 getClass().getName() + ". Tag: " + wTag.toString() + 083 ", expected tag: <wicket:border>"); 084 } 085 } 086 else 087 { 088 if (wTag.isBodyTag()) 089 { 090 break; 091 } 092 else 093 { 094 throw new WicketRuntimeException( 095 "Unexpected tag encountered in markup of component border " + 096 getClass().getName() + ". Tag: " + wTag.toString() + 097 ", expected tag: <wicket:body> or </wicket:body>"); 098 } 099 } 100 } 101 if (insideBorderMarkup) 102 { 103 response.write(elem.toCharSequence()); 104 } 105 } 106 107 if (!stream.isCurrentIndexInsideTheStream()) 108 { 109 throw new WicketRuntimeException("Markup for component border " + getClass().getName() + 110 " ended prematurely, was expecting </wicket:border>"); 111 } 112 } 113 114 @Override 115 public void afterRender(final Component component) 116 { 117 final MarkupStream stream = getMarkupStream(component); 118 final Response response = component.getResponse(); 119 120 while (stream.isCurrentIndexInsideTheStream()) 121 { 122 MarkupElement elem = stream.get(); 123 stream.next(); 124 if (elem instanceof WicketTag) 125 { 126 WicketTag wTag = (WicketTag)elem; 127 if (wTag.isBorderTag() && wTag.isClose()) 128 { 129 break; 130 } 131 else 132 { 133 throw new WicketRuntimeException( 134 "Unexpected tag encountered in markup of component border " + 135 getClass().getName() + ". Tag: " + wTag.toString() + 136 ", expected tag: </wicket:border>"); 137 } 138 } 139 response.write(elem.toCharSequence()); 140 } 141 } 142 143 /** 144 * 145 * @param component 146 * @return markup stream 147 */ 148 private MarkupStream getMarkupStream(final Component component) 149 { 150 if (markupStream == null) 151 { 152 markupStream = findMarkupStream(component); 153 } 154 return markupStream; 155 } 156 157 /** 158 * 159 * @param owner 160 * @return markup stream 161 */ 162 private MarkupStream findMarkupStream(final Component owner) 163 { 164 final MarkupType markupType = getMarkupType(owner); 165 if (markupType == null) 166 { 167 return null; 168 } 169 170 // TODO we need to expose this functionality for any class not just for 171 // markupcontainers in markupcache so we don't have to replicate this 172 // logic here 173 174 // Get locator to search for the resource 175 final IResourceStreamLocator locator = Application.get() 176 .getResourceSettings() 177 .getResourceStreamLocator(); 178 179 final String style = owner.getStyle(); 180 final String variation = owner.getVariation(); 181 final Locale locale = owner.getLocale(); 182 183 MarkupResourceStream markupResourceStream = null; 184 Class<?> containerClass = getClass(); 185 186 while (!(containerClass.equals(BorderBehavior.class))) 187 { 188 String path = containerClass.getName().replace('.', '/'); 189 IResourceStream resourceStream = locator.locate(containerClass, path, style, variation, 190 locale, markupType.getExtension(), false); 191 192 // Did we find it already? 193 if (resourceStream != null) 194 { 195 ContainerInfo ci = new ContainerInfo(containerClass, locale, style, variation, 196 markupType); 197 markupResourceStream = new MarkupResourceStream(resourceStream, ci, containerClass); 198 break; 199 } 200 201 // Walk up the class hierarchy one level, if markup has not 202 // yet been found 203 containerClass = containerClass.getSuperclass(); 204 } 205 206 if (markupResourceStream == null) 207 { 208 throw new WicketRuntimeException("Could not find markup for component border `" + 209 getClass().getName() + "`"); 210 } 211 212 try 213 { 214 IMarkupFragment markup = MarkupFactory.get() 215 .newMarkupParser(markupResourceStream) 216 .parse(); 217 218 return new MarkupStream(markup); 219 } 220 catch (Exception e) 221 { 222 throw new WicketRuntimeException( 223 "Could not parse markup from markup resource stream: " + 224 markupResourceStream.toString(), e); 225 } 226 finally 227 { 228 try 229 { 230 markupResourceStream.close(); 231 } 232 catch (IOException e) 233 { 234 throw new WicketRuntimeException("Cannot close markup resource stream: " + 235 markupResourceStream, e); 236 } 237 } 238 } 239 240 /** 241 * 242 * @param component 243 * @return markup type 244 */ 245 private MarkupType getMarkupType(final Component component) 246 { 247 final MarkupType markupType; 248 if (component instanceof MarkupContainer) 249 { 250 markupType = ((MarkupContainer)component).getMarkupType(); 251 } 252 else 253 { 254 markupType = component.getParent().getMarkupType(); 255 } 256 return markupType; 257 } 258}