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; 018 019import java.io.IOException; 020 021import org.apache.wicket.Application; 022import org.apache.wicket.MarkupContainer; 023import org.apache.wicket.WicketRuntimeException; 024import org.apache.wicket.markup.loader.DefaultMarkupLoader; 025import org.apache.wicket.markup.loader.IMarkupLoader; 026import org.apache.wicket.markup.parser.IMarkupFilter; 027import org.apache.wicket.markup.parser.IXmlPullParser; 028import org.apache.wicket.markup.parser.XmlPullParser; 029import org.apache.wicket.util.lang.Args; 030import org.apache.wicket.util.resource.IResourceStream; 031import org.apache.wicket.util.resource.ResourceStreamNotFoundException; 032import org.slf4j.Logger; 033import org.slf4j.LoggerFactory; 034 035/** 036 * Factory to load markup either from cache or from a resource. 037 * <p> 038 * This class is the main entry point to load markup. Nothing else should be required by Components. 039 * It manages caching markup as well as loading and merging (inheritance) of markup. 040 * <p> 041 * The markup returned is immutable as it gets re-used across multiple Component instances. 042 * 043 * @author Juergen Donnerstag 044 */ 045public class MarkupFactory 046{ 047 /** Log for reporting. */ 048 private static final Logger log = LoggerFactory.getLogger(MarkupFactory.class); 049 050 /** A markup cache */ 051 private IMarkupCache markupCache = null; 052 053 /** The markup resource stream provider used by MarkupCache */ 054 private IMarkupResourceStreamProvider markupResourceStreamProvider = null; 055 056 /** 057 * @return Gets the markup factory registered with the Wicket application 058 */ 059 public static MarkupFactory get() 060 { 061 return Application.get().getMarkupSettings().getMarkupFactory(); 062 } 063 064 /** 065 * Construct. 066 */ 067 public MarkupFactory() 068 { 069 } 070 071 /** 072 * MarkupLoaders are responsible to find and load the markup for a component. That may be a 073 * single file, but e.g. like in markup inheritance it could also be that the markup from 074 * different sources must be merged. 075 * 076 * @return By default an instance of {@link DefaultMarkupLoader} will be returned. Via 077 * subclassing you may return your own markup loader (chain). 078 */ 079 public IMarkupLoader getMarkupLoader() 080 { 081 return new DefaultMarkupLoader(); 082 } 083 084 /** 085 * Create a new markup parser. Markup parsers read the markup and dissect it in Wicket relevant 086 * pieces {@link MarkupElement}'s (kind of Wicket's DOM). 087 * <p> 088 * MarkupParser's can be extended by means of {@link IMarkupFilter}. You can add your own filter 089 * as follows: 090 * 091 * <pre> 092 * public MyMarkupFactory { 093 * ... 094 * public MarkupParser newMarkupParser(final MarkupResourceStream resource) { 095 * MarkupParser parser = super.newMarkupParser(resource); 096 * parser.add(new MyFilter()); 097 * return parser; 098 * } 099 * } 100 * </pre> 101 * 102 * @see #onAppendMarkupFilter(IMarkupFilter) 103 * 104 * @param resource 105 * The resource containing the markup 106 * @return A fresh instance of {@link MarkupParser} 107 */ 108 public MarkupParser newMarkupParser(final MarkupResourceStream resource) 109 { 110 // Markup parsers can not be re-used 111 return new MarkupParser(newXmlPullParser(), resource) 112 { 113 @Override 114 protected IMarkupFilter onAppendMarkupFilter(final IMarkupFilter filter) 115 { 116 return MarkupFactory.this.onAppendMarkupFilter(filter); 117 } 118 }; 119 } 120 121 /** 122 * Subclasses can override this to use custom parsers. 123 * 124 * @return parser instance used by {@link MarkupParser} to parse markup. 125 */ 126 protected IXmlPullParser newXmlPullParser() 127 { 128 return new XmlPullParser(); 129 } 130 131 /** 132 * A callback method that is invoked prior to any {@link IMarkupFilter} being registered with 133 * {@link MarkupParser}. Hence it allows to: 134 * <ul> 135 * <li>tweak the default configuration of a filter</li> 136 * <li>replace a filter with another one</li> 137 * <li>avoid filters being used by returning null</li> 138 * </ul> 139 * Note that a new {@link MarkupParser} instance is created for each markup resources being 140 * loaded. 141 * <p> 142 * 143 * @param filter 144 * The filter to be registered with the MarkupParser 145 * @return The filter to be added. Null to ignore. 146 */ 147 protected IMarkupFilter onAppendMarkupFilter(final IMarkupFilter filter) 148 { 149 return filter; 150 } 151 152 /** 153 * Get the markup cache which is registered with the factory. Since the factory is registered 154 * with the application, only one cache per application exists. 155 * <p> 156 * Please note that markup cache is a pull through cache. It'll invoke a factory method 157 * {@link #getMarkupResourceStream(MarkupContainer, Class)} to load the markup if not yet 158 * available in the cache. 159 * 160 * @return Null, to disable caching. 161 */ 162 public IMarkupCache getMarkupCache() 163 { 164 if (markupCache == null) 165 { 166 markupCache = new MarkupCache(); 167 } 168 169 return markupCache; 170 } 171 172 /** 173 * @return <code>true</code> if markup cache is available. Make sure you called 174 * {@link #getMarkupCache()} at least once before to initialize the cache. 175 */ 176 public boolean hasMarkupCache() 177 { 178 return markupCache != null; 179 } 180 181 /** 182 * Get the markup associated with the container. 183 * 184 * @param container 185 * The container to find the markup for 186 * @param enforceReload 187 * If true, the cache will be ignored and all, including inherited markup files, will 188 * be reloaded. Whatever is in the cache, it will be ignored 189 * @return The markup associated with the container. Null, if the markup was not found or could 190 * not yet be loaded (e.g. getMarkupType() == null). Wicket Exception in case of errors. 191 */ 192 public final Markup getMarkup(final MarkupContainer container, final boolean enforceReload) 193 { 194 return getMarkup(container, container.getClass(), enforceReload); 195 } 196 197 /** 198 * Get the markup associated with the container. Check the cache first. If not found, than load 199 * the markup and update the cache. 200 * <p> 201 * The clazz parameter usually can be null, except for base (inherited) markup. 202 * <p> 203 * There are several means to disable markup caching. Caching can be disabled alltogether - 204 * getMarkupCache() return null -, or individually (cacheKey == null). 205 * 206 * @param container 207 * The container to find the markup for 208 * @param clazz 209 * Must be the container class or any of its super classes. May be null. 210 * @param enforceReload 211 * The cache will be ignored and all, including inherited markup files, will be 212 * reloaded. Whatever is in the cache, it will be ignored 213 * @return The markup associated with the container. Null, if the markup was not found or could 214 * not yet be loaded (e.g. getMarkupType() == null). Wicket Exception in case of errors. 215 */ 216 public final Markup getMarkup(final MarkupContainer container, final Class<?> clazz, 217 final boolean enforceReload) 218 { 219 Args.notNull(container, "container"); 220 221 if (checkMarkupType(container) == false) 222 { 223 // TODO improve: Result { boolean success, enum FailureReason {not found, not yet 224 // available}, Markup markup } 225 return null; 226 } 227 228 Class<?> containerClass = getContainerClass(container, clazz); 229 230 IMarkupCache cache = getMarkupCache(); 231 if (cache != null) 232 { 233 // MarkupCache acts as pull-through cache. It'll call the same loadMarkup() method as 234 // below, if needed. 235 // @TODO may be that can be changed. I don't like it too much. 236 return cache.getMarkup(container, containerClass, enforceReload); 237 } 238 239 // Get the markup resource stream for the container (and super class) 240 MarkupResourceStream markupResourceStream = getMarkupResourceStream(container, 241 containerClass); 242 243 return loadMarkup(container, markupResourceStream, enforceReload); 244 } 245 246 /** 247 * Without a markup type we can not search for a file and we can not construct the cacheKey. We 248 * can not even load associated markup as required for Panels. Though every MarkupContainer can 249 * provide it's own type, by default they refer to the Page. Hence, no markup type is an 250 * indicator, that the component or any of its parents, has not yet been added. 251 * 252 * @param container 253 * The MarkupContainer which markup type has to checked 254 * @return true, if container.getMarkupType() != null 255 */ 256 protected final boolean checkMarkupType(final MarkupContainer container) 257 { 258 if (container.getMarkupType() == null) 259 { 260 log.debug("Markup file not loaded, since the markup type is not yet available: {}", container); 261 return false; 262 } 263 264 return true; 265 } 266 267 /** 268 * Get the markup resource stream provider registered with the factory. 269 * <p> 270 * If the 'container' implements {@link IMarkupResourceStreamProvider}, the container itself 271 * will be asked to provide the resource stream. Else Wicket's default implementation will be 272 * used. 273 * 274 * @param container 275 * The MarkupContainer requesting the markup resource stream 276 * @return IMarkupResourceStreamProvider 277 */ 278 protected final IMarkupResourceStreamProvider getMarkupResourceStreamProvider( 279 final MarkupContainer container) 280 { 281 if (container instanceof IMarkupResourceStreamProvider) 282 { 283 return (IMarkupResourceStreamProvider)container; 284 } 285 286 if (markupResourceStreamProvider == null) 287 { 288 markupResourceStreamProvider = new DefaultMarkupResourceStreamProvider(); 289 } 290 return markupResourceStreamProvider; 291 } 292 293 /** 294 * Create a new markup resource stream for the container and optionally the Class. The Class 295 * must be provided in case of base (inherited) markup. Else it might be null (standard use 296 * case). 297 * 298 * @param container 299 * The MarkupContainer which requests to load the Markup resource stream 300 * @param clazz 301 * Either the container class or any super class. Might be null. 302 * @return A IResourceStream if the resource was found 303 */ 304 public final MarkupResourceStream getMarkupResourceStream(final MarkupContainer container, 305 Class<?> clazz) 306 { 307 Args.notNull(container, "container"); 308 309 if (checkMarkupType(container) == false) 310 { 311 // TODO improve: Result { boolean success, enum FailureReason {not found, not yet 312 // available}, Markup markup } 313 return null; 314 } 315 316 Class<?> containerClass = getContainerClass(container, clazz); 317 318 // Who is going to provide the markup resource stream? 319 // And ask the provider to locate the markup resource stream 320 final IResourceStream resourceStream = getMarkupResourceStreamProvider(container).getMarkupResourceStream( 321 container, containerClass); 322 323 // Found markup? 324 if (resourceStream == null) 325 { 326 // TODO improve: Result { boolean success, enum FailureReason {not found, not yet 327 // available}, Markup markup } 328 return null; 329 } 330 331 if (resourceStream instanceof MarkupResourceStream) 332 { 333 return (MarkupResourceStream)resourceStream; 334 } 335 336 return new MarkupResourceStream(resourceStream, new ContainerInfo(container), 337 containerClass); 338 } 339 340 /** 341 * Gets and checks the container class 342 * 343 * @param container 344 * The MarkupContainer which requests to load the Markup resource stream 345 * @param clazz 346 * Either null, or a super class of container 347 * @return The container class to be used 348 */ 349 public final Class<?> getContainerClass(final MarkupContainer container, final Class<?> clazz) 350 { 351 Args.notNull(container, "container"); 352 353 Class<?> containerClass = clazz; 354 if (clazz == null) 355 { 356 containerClass = container.getClass(); 357 } 358 else if (!clazz.isAssignableFrom(container.getClass())) 359 { 360 throw new IllegalArgumentException("Parameter clazz must be an instance of " + 361 container.getClass().getName() + ", but is a " + clazz.getName()); 362 } 363 return containerClass; 364 } 365 366 /** 367 * Loads markup from a resource stream. It'll call the registered markup loader to load the 368 * markup. 369 * <p> 370 * Though the 'enforceReload' attribute seem to imply that the cache is consulted to retrieve 371 * the markup, the cache in fact is only checked for retrieving the base (inherited) markup. 372 * Please see {@link #getMarkup(MarkupContainer, boolean)} as well. 373 * 374 * @param container 375 * The original requesting markup container 376 * @param markupResourceStream 377 * The markup resource stream to load, if already known. 378 * @param enforceReload 379 * The cache will be ignored and all, including inherited markup files, will be 380 * reloaded. Whatever is in the cache, it will be ignored 381 * @return The markup. Null, if the markup was not found. Wicket Exception in case of errors. 382 */ 383 public final Markup loadMarkup(final MarkupContainer container, 384 final MarkupResourceStream markupResourceStream, final boolean enforceReload) 385 { 386 // @TODO can markupResourceStream be replace with clazz??? 387 Args.notNull(container, "container"); 388 Args.notNull(markupResourceStream, "markupResourceStream"); 389 390 if (checkMarkupType(container) == false) 391 { 392 // TODO improve: Result { boolean success, enum FailureReason {not found, not yet 393 // available}, Markup markup } 394 return null; 395 } 396 397 try 398 { 399 // The InheritedMarkupMarkupLoader needs to load the base markup. It'll do it via 400 // MarkupFactory.getMarkup() as main entry point, which in turn allows to choose between 401 // use or ignore the cache. That's why we need to propagate enforceReload to the markup 402 // loader as well. 403 404 // Markup loader is responsible to load the full markup for the container. In case of 405 // markup inheritance, the markup must be merged from different markup files. It is the 406 // merged markup which eventually will be cached, thus avoiding repetitive merge 407 // operations, which always result in the same outcome. 408 // The base markup will still be cached though, in order to avoid any unnecessary 409 // reloads. The base markup itself might be merged as it might inherit from its base 410 // class. 411 412 return getMarkupLoader().loadMarkup(container, markupResourceStream, null, 413 enforceReload); 414 } 415 catch (MarkupNotFoundException e) 416 { 417 // InheritedMarkupMarkupLoader will throw a MarkupNotFoundException in case the 418 // <b>base</b> markup can not be found. 419 420 log.error("Markup not found: " + e.getMessage(), e); 421 422 // Catch exception and ignore => return null (markup not found) 423 } 424 catch (ResourceStreamNotFoundException e) 425 { 426 log.error("Markup not found: " + markupResourceStream, e); 427 428 // Catch exception and ignore => return null (markup not found) 429 } 430 catch (IOException e) 431 { 432 log.error("Error while reading the markup " + markupResourceStream, e); 433 434 // Wrap with wicket exception and re-throw 435 throw new MarkupException(markupResourceStream, "IO error while reading markup: " + 436 e.getMessage(), e); 437 } 438 catch (WicketRuntimeException e) 439 { 440 log.error("Error while reading the markup " + markupResourceStream, e); 441 442 // re-throw 443 throw e; 444 } 445 catch (RuntimeException e) 446 { 447 log.error("Error while reading the markup " + markupResourceStream, e); 448 449 // Wrap with wicket exception and re-throw 450 throw new MarkupException(markupResourceStream, "Error while reading the markup: " + 451 e.getMessage(), e); 452 } 453 454 // Markup not found. Errors should throw a Wicket exception 455 return null; 456 } 457}