All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.wicket.markup.MarkupCache Maven / Gradle / Ivy

Go to download

Pax Wicket Service is an OSGi extension of the Wicket framework, allowing for dynamic loading and unloading of Wicket components and pageSources.

There is a newer version: 5.0.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.wicket.markup;

import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.wicket.Application;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.WicketRuntimeException;
import org.apache.wicket.markup.loader.DefaultMarkupLoader;
import org.apache.wicket.markup.loader.IMarkupLoader;
import org.apache.wicket.settings.IMarkupSettings;
import org.apache.wicket.util.listener.IChangeListener;
import org.apache.wicket.util.resource.IResourceStream;
import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
import org.apache.wicket.util.watch.IModifiable;
import org.apache.wicket.util.watch.IModificationWatcher;
import org.apache.wicket.util.watch.ModificationWatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This is Wicket's default IMarkupCache implementation. It will load the markup and cache it for
 * fast retrieval.
 * 

* If the application is in development mode and a markup file changes, it'll automatically be * removed from the cache and reloaded when needed. *

* MarkupCache is registered with {@link IMarkupSettings} and thus can be replaced with a subclassed * version. * * @see IMarkupSettings * * @author Jonathan Locke * @author Juergen Donnerstag */ public class MarkupCache implements IMarkupCache { /** Log for reporting. */ private static final Logger log = LoggerFactory.getLogger(MarkupCache.class); /** Map of markup tags by class (exactly what is in the file). */ private final ICache markupCache; /** Map of markup tags by class (exactly what is in the file). */ private final ICache markupKeyCache; /** The markup cache key provider used by MarkupCache */ private IMarkupCacheKeyProvider markupCacheKeyProvider; /** The markup resource stream provider used by MarkupCache */ private IMarkupResourceStreamProvider markupResourceStreamProvider; /** The markup loader used by MarkupCache */ private IMarkupLoader markupLoader; /** The application object */ private final Application application; /** * Constructor. * * @param application */ public MarkupCache(Application application) { this.application = application; markupCache = newCacheImplementation(); markupKeyCache = newCacheImplementation(); if (markupCache == null) { throw new WicketRuntimeException("The map used to cache markup must not be null"); } } /** * @see org.apache.wicket.markup.IMarkupCache#clear() */ public final void clear() { markupCache.clear(); markupKeyCache.clear(); } /** * * @see org.apache.wicket.markup.IMarkupCache#shutdown() */ public void shutdown() { markupCache.shutdown(); markupKeyCache.shutdown(); } /** * @see org.apache.wicket.markup.IMarkupCache#removeMarkup(java.lang.String) */ public final Markup removeMarkup(final String cacheKey) { if (cacheKey == null) { throw new IllegalArgumentException("Parameter 'cacheKey' must not be null"); } if (log.isDebugEnabled()) { log.debug("Remove from cache: cacheKey=" + cacheKey); } // Remove the markup and any other markup which depends on it // (inheritance) String locationString = (String)markupKeyCache.get(cacheKey); Markup markup = markupCache.get(locationString); if (markup != null) { markupCache.remove(locationString); // In practice markup inheritance has probably not more than 3 or 4 // levels. And since markup reloading is only enabled in development // mode, this max 4 iterations of the outer loop shouldn't be a // problem. int count; do { count = 0; // If a base markup file has been removed from the cache, than // the derived markup should be removed as well. Iterator iter = markupCache.getKeys().iterator(); while (iter.hasNext()) { Markup cacheMarkup = markupCache.get(iter.next()); MarkupResourceData resourceData = cacheMarkup.getMarkupResourceData() .getBaseMarkupResourceData(); if (resourceData != null) { String baseCacheKey = resourceData.getResource().getCacheKey(); String baseLocationString = (String)markupKeyCache.get(baseCacheKey); if (baseLocationString != null && markupCache.get(baseLocationString) == null) { if (log.isDebugEnabled()) { log.debug("Remove from cache: cacheKey=" + cacheMarkup.getMarkupResourceData().getResource().getCacheKey()); } iter.remove(); count++; } } } } while (count > 0); // And now remove all watcher entries associated with markup // resources no longer in the cache. Note that you can not use // Application.get() since removeMarkup() will be call from a // ModificationWatcher thread which has no associated Application. final IModificationWatcher watcher = application.getResourceSettings() .getResourceWatcher(true); if (watcher != null) { Iterator iter = watcher.getEntries().iterator(); while (iter.hasNext()) { IModifiable modifiable = iter.next(); if (modifiable instanceof MarkupResourceStream) { MarkupResourceStream resourceStream = (MarkupResourceStream)modifiable; String resourceCacheKey = resourceStream.getCacheKey(); String resouceLocationString = (String)markupKeyCache.get(resourceCacheKey); if (resouceLocationString != null && markupCache.containsKey(resouceLocationString) == false) { iter.remove(); } } } } } return markup; } /** * @see org.apache.wicket.markup.IMarkupCache#getMarkupStream(org.apache.wicket.MarkupContainer, * boolean, boolean) */ public final MarkupStream getMarkupStream(final MarkupContainer container, final boolean enforceReload, final boolean throwException) { if (container == null) { throw new IllegalArgumentException("Parameter 'container' must not be 'null'."); } // Look for associated markup final Markup markup = getMarkup(container, container.getClass(), enforceReload); // If we found markup for this container if (markup != Markup.NO_MARKUP) { return new MarkupStream(markup); } if (throwException == true) { // throw exception since there is no associated markup throw new MarkupNotFoundException( "Markup of type '" + container.getMarkupType() + "' for component '" + container.getClass().getName() + "' not found." + " Enable debug messages for org.apache.wicket.util.resource to get a list of all filenames tried.: " + container.toString()); } return null; } /** * @see org.apache.wicket.markup.IMarkupCache#hasAssociatedMarkup(org.apache.wicket.MarkupContainer) */ public final boolean hasAssociatedMarkup(final MarkupContainer container) { return getMarkup(container, container.getClass(), false) != Markup.NO_MARKUP; } /** * @see org.apache.wicket.markup.IMarkupCache#size() */ public final int size() { return markupCache.size(); } /** * Get a unmodifiable map which contains the cached data. The map key is of type String and the * value is of type Markup. * * @return cache implementation */ protected final ICache getMarkupCache() { return markupCache; } /** * THIS IS NOT PART OF WICKET'S PUBLIC API. DO NOT USE IT. * * I still don't like this method being part of the API but I didn't find a suitable other * solution. * * @see org.apache.wicket.markup.IMarkupCache#getMarkup(org.apache.wicket.MarkupContainer, * java.lang.Class, boolean) */ public final Markup getMarkup(final MarkupContainer container, final Class clazz, final boolean enforceReload) { Class containerClass = clazz; if (clazz == null) { containerClass = container.getClass(); } else if (!clazz.isAssignableFrom(container.getClass())) { throw new WicketRuntimeException("Parameter clazz must be an instance of " + container.getClass().getName() + ", but is a " + clazz.getName()); } // Get the cache key to be associated with the markup resource stream final String cacheKey = getMarkupCacheKeyProvider(container).getCacheKey(container, containerClass); // Is the markup already in the cache? Markup markup = (enforceReload == false ? getMarkupFromCache(cacheKey, container) : null); if (markup == null) { if (log.isDebugEnabled()) { log.debug("Load markup: cacheKey=" + cacheKey); } // Who is going to provide the markup resource stream? // And ask the provider to locate the markup resource stream final IResourceStream resourceStream = getMarkupResourceStreamProvider(container).getMarkupResourceStream( container, containerClass); // Found markup? if (resourceStream != null) { final MarkupResourceStream markupResourceStream; if (resourceStream instanceof MarkupResourceStream) { markupResourceStream = (MarkupResourceStream)resourceStream; } else { markupResourceStream = new MarkupResourceStream(resourceStream, new ContainerInfo(container), containerClass); } markupResourceStream.setCacheKey(cacheKey); // load the markup and watch for changes markup = loadMarkupAndWatchForChanges(container, markupResourceStream, enforceReload); } else { markup = onMarkupNotFound(cacheKey, container); } } return markup; } /** * Will be called if the markup was not in the cache yet but could not be found either. *

* Subclasses may change the default implementation. E.g. they might choose not update the cache * to enforce reloading of any markup not found. This might be useful in very dynamic * environments. * * @param cacheKey * @param container * @return Markup.NO_MARKUP */ protected Markup onMarkupNotFound(final String cacheKey, final MarkupContainer container) { if (log.isDebugEnabled()) { log.debug("Markup not found: " + cacheKey); } // flag markup as non-existent markupKeyCache.put(cacheKey, cacheKey); return putIntoCache(cacheKey, container, Markup.NO_MARKUP); } /** * Put the markup into the cache if cacheKey is not null and the cache does not yet contain the * cacheKey. Return the markup stored in the cache if cacheKey is present already. * * @param locationString * If null, than ignore the cache * @param markup * @return markup The markup provided, except if the cacheKey already existed in the cache, than * the markup from the cache is provided. * * @deprecated see {@link #putIntoCache(String, MarkupContainer, Markup)} */ @Deprecated protected Markup putIntoCache(final String locationString, Markup markup) { if (locationString != null) { if (markupCache.containsKey(locationString) == false) { markupCache.put(locationString, markup); } else { // We don't lock the cache while loading a markup. Thus it may // happen that the very same markup gets loaded twice (the first // markup being loaded, but not yet in the cache, and another // request requesting the very same markup). Since markup // loading in avg takes less than 100ms, it is not really an // issue. For consistency reasons however, we should always use // the markup loaded first which is why it gets returned. markup = markupCache.get(locationString); } } return markup; } /** * Put the markup into the cache if cacheKey is not null and the cache does not yet contain the * cacheKey. Return the markup stored in the cache if cacheKey is present already. * * More sophisticated implementations may call a container method to e.g. cache it per container * instance. * * @param locationString * If null, than ignore the cache * @param container * The container this markup is for. * @param markup * @return markup The markup provided, except if the cacheKey already existed in the cache, than * the markup from the cache is provided. */ protected Markup putIntoCache(final String locationString, MarkupContainer container, Markup markup) { return putIntoCache(locationString, markup); } /** * Wicket's default implementation just uses the cacheKey to retrieve the markup from the cache. * More sophisticated implementations may call a container method to e.g. ignore the cached * markup under certain situations. * * @param cacheKey * If null, than the cache will be ignored * @param container * @return null, if not found or to enforce reloading the markup */ protected Markup getMarkupFromCache(final CharSequence cacheKey, final MarkupContainer container) { if (cacheKey != null) { String locationString = (String)markupKeyCache.get(cacheKey); if (locationString != null) { return markupCache.get(locationString); } } return null; } /** * Loads markup from a resource stream. * * @param container * The original requesting markup container * @param markupResourceStream * The markup resource stream to load * @param enforceReload * The cache will be ignored and all, including inherited markup files, will be * reloaded. Whatever is in the cache, it will be ignored * @return The markup */ private final Markup loadMarkup(final MarkupContainer container, final MarkupResourceStream markupResourceStream, final boolean enforceReload) { String cacheKey = markupResourceStream.getCacheKey(); String locationString = markupResourceStream.locationAsString(); if (locationString == null) { // set the cache key as location string, because location string // couldn't be resolved. locationString = cacheKey; } try { Markup markup = getMarkupLoader().loadMarkup(container, markupResourceStream, null, enforceReload); if (cacheKey != null) { String temp = markup.locationAsString(); if (temp != null) { locationString = temp; } // add the markup to the cache. markupKeyCache.put(cacheKey, locationString); return putIntoCache(locationString, container, markup); } return markup; } catch (ResourceStreamNotFoundException e) { log.error("Unable to find markup from " + markupResourceStream, e); } catch (IOException e) { log.error("Unable to read markup from " + markupResourceStream, e); } // In case of an error, remove the cache entry if (cacheKey != null) { removeMarkup(cacheKey); } return Markup.NO_MARKUP; } /** * Load markup from an IResourceStream and add an {@link IChangeListener}to the * {@link ModificationWatcher} so that if the resource changes, we can remove it from the cache * automatically and subsequently reload when needed. * * @param container * The original requesting markup container * @param markupResourceStream * The markup stream to load and begin to watch * @param enforceReload * The cache will be ignored and all, including inherited markup files, will be * reloaded. Whatever is in the cache, it will be ignored * @return The markup in the stream */ private final Markup loadMarkupAndWatchForChanges(final MarkupContainer container, final MarkupResourceStream markupResourceStream, final boolean enforceReload) { final String cacheKey = markupResourceStream.getCacheKey(); if (cacheKey != null) { // get the location String String locationString = markupResourceStream.locationAsString(); if (locationString == null) { // set the cache key as location string, because location string // couldn't be resolved. locationString = cacheKey; } Markup markup = markupCache.get(locationString); if (markup != null) { markupKeyCache.put(cacheKey, locationString); return markup; } // Watch file in the future final IModificationWatcher watcher = Application.get() .getResourceSettings() .getResourceWatcher(true); if (watcher != null) { watcher.add(markupResourceStream, new IChangeListener() { public void onChange() { if (log.isDebugEnabled()) { log.debug("Remove markup from cache: " + markupResourceStream); } // Remove the markup from the cache. It will be reloaded // next time when the markup is requested. watcher.remove(markupResourceStream); removeMarkup(cacheKey); } }); } } if (log.isDebugEnabled()) { log.debug("Loading markup from " + markupResourceStream); } return loadMarkup(container, markupResourceStream, enforceReload); } /** * Get the markup cache key provider to be used * * @param container * The MarkupContainer requesting the markup resource stream * @return IMarkupResourceStreamProvider */ public IMarkupCacheKeyProvider getMarkupCacheKeyProvider(final MarkupContainer container) { if (container instanceof IMarkupCacheKeyProvider) { return (IMarkupCacheKeyProvider)container; } if (markupCacheKeyProvider == null) { markupCacheKeyProvider = new DefaultMarkupCacheKeyProvider(); } return markupCacheKeyProvider; } /** * Get the markup resource stream provider to be used * * @param container * The MarkupContainer requesting the markup resource stream * @return IMarkupResourceStreamProvider */ protected IMarkupResourceStreamProvider getMarkupResourceStreamProvider( final MarkupContainer container) { if (container instanceof IMarkupResourceStreamProvider) { return (IMarkupResourceStreamProvider)container; } if (markupResourceStreamProvider == null) { markupResourceStreamProvider = new DefaultMarkupResourceStreamProvider(); } return markupResourceStreamProvider; } /** * In case there is a need to extend the default chain of MarkupLoaders * * @return MarkupLoader */ protected IMarkupLoader getMarkupLoader() { if (markupLoader == null) { markupLoader = new DefaultMarkupLoader(); } return markupLoader; } /** * Allows you to change the map implementation which will hold the cache data. By default it is * a ConcurrentHashMap() in order to allow multiple thread to access the data in a secure way. * * @param * @param * * @return new instance of cache implementation */ protected ICache newCacheImplementation() { return new DefaultCacheImplementation(); } /** * MarkupCache allows you to implement you own cache implementation. ICache is the interface the * implementation must comply with. * * @param * The key type * @param * The value type * * @see MarkupCache */ public interface ICache { /** * Clear the cache */ void clear(); /** * Remove an entry from the cache. * * @param key * @return true, if found and removed */ boolean remove(K key); /** * Get the cache element associated with the key * * @param key * @return cached object for key key or null if no matches */ V get(K key); /** * Get all the keys referencing cache entries * * @return collection of cached keys */ Collection getKeys(); /** * Check if key is in the cache * * @param key * @return true if cache contains key key */ boolean containsKey(K key); /** * Get the number of cache entries * * @return number of cache entries */ int size(); /** * Put an entry into the cache * * @param key * The reference key to find the element * @param value * The element to be cached */ void put(K key, V value); /** * Cleanup and shutdown */ void shutdown(); } /** * @param * @param * */ public class DefaultCacheImplementation implements ICache { private static final long serialVersionUID = 1L; private final ConcurrentHashMap cache = new ConcurrentHashMap(); /** * Construct. */ public DefaultCacheImplementation() { } /** * @see org.apache.wicket.markup.MarkupCache.ICache#clear() */ public void clear() { cache.clear(); } /** * @see org.apache.wicket.markup.MarkupCache.ICache#containsKey(java.lang.Object) */ public boolean containsKey(Object key) { return cache.containsKey(key); } /** * @see org.apache.wicket.markup.MarkupCache.ICache#get(java.lang.Object) */ public V get(Object key) { if (key == null) { return null; } return cache.get(key); } /** * @see org.apache.wicket.markup.MarkupCache.ICache#getKeys() */ public Collection getKeys() { return cache.keySet(); } /** * @see org.apache.wicket.markup.MarkupCache.ICache#put(java.lang.Object, java.lang.Object) */ public void put(K key, V value) { cache.put(key, value); } /** * @see org.apache.wicket.markup.MarkupCache.ICache#remove(java.lang.Object) */ public boolean remove(K key) { return cache.remove(key) == null; } /** * @see org.apache.wicket.markup.MarkupCache.ICache#size() */ public int size() { return cache.size(); } /** * @see org.apache.wicket.markup.MarkupCache.ICache#shutdown() */ public void shutdown() { clear(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy