/** * 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 String cacheKey, final MarkupContainer container) { if (cacheKey != null) { String locationString = markupKeyCache.get(cacheKey); if (locationString != null) { return markupCache.get(locationString); } } return null; }
@Override public boolean flushEntry(IFileSpec entry) { boolean result = _cache.flushEntry(entry); int key = _cache.getCacheKey(entry).getKey(); if (result) { log(String.format("Flushed %s: %d", entry.getPreferredPathString(), key)); } else { log(String.format("Could not flush %s: %d", entry.getPreferredPathString(), key)); } return result; }
@Override public IFileSpec putEntry(IFileSpec entry) { IFileSpec result = _cache.putEntry(entry); int key = _cache.getCacheKey(entry).getKey(); if (null != result) { log(String.format("Cached existing %s: %d", entry.getPreferredPathString(), key)); } else { log(String.format("Cached new entry for %s: %d", entry.getPreferredPathString(), key)); } return result; }
/** * @param resourceStream * @return True if the markup is cached */ private boolean isMarkupCached(final MarkupResourceStream resourceStream) { if (resourceStream != null) { String key = resourceStream.getCacheKey(); if (key != null) { String locationString = markupKeyCache.get(key); if ((locationString != null) && (markupCache.get(locationString) != null)) { return true; } } } return false; }
/** * Note that this method will be called from a "cleanup" thread which might not have a thread * local application. */ @Override public final IMarkupFragment removeMarkup(final String cacheKey) { Args.notNull(cacheKey, "cacheKey"); if (log.isDebugEnabled()) { log.debug("Removing from cache: " + cacheKey); } // Remove the markup from the cache String locationString = markupKeyCache.get(cacheKey); IMarkupFragment markup = (locationString != null ? markupCache.get(locationString) : null); if (markup == null) { return null; } // Found an entry: actual markup or Markup.NO_MARKUP. Null values are not possible // because of ConcurrentHashMap. markupCache.remove(locationString); if (log.isDebugEnabled()) { log.debug("Removed from cache: " + locationString); } // If a base markup file has been removed from the cache then // the derived markup should be removed as well. removeMarkupWhereBaseMarkupIsNoLongerInTheCache(); // 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 called from a // ModificationWatcher thread which has no associated Application. IModificationWatcher watcher = application.getResourceSettings().getResourceWatcher(false); if (watcher != null) { Iterator<IModifiable> iter = watcher.getEntries().iterator(); while (iter.hasNext()) { IModifiable modifiable = iter.next(); if (modifiable instanceof MarkupResourceStream) { if (!isMarkupCached((MarkupResourceStream) modifiable)) { iter.remove(); if (log.isDebugEnabled()) { log.debug("Removed from watcher: " + modifiable); } } } } } return 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) { // @TODO the following code sequence looks very much like in loadMarkup. Can it be // optimized? final String cacheKey = markupResourceStream.getCacheKey(); if (cacheKey != null) { if (enforceReload == false) { // 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.getResourceSettings().getResourceWatcher(true); if (watcher != null) { watcher.add( markupResourceStream, new IChangeListener() { @Override public void onChange() { if (log.isDebugEnabled()) { log.debug("Remove markup from watcher: " + 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); }
/** * 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. Markup.NO_MARKUP, if not found. */ 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; } Markup markup = MarkupFactory.get().loadMarkup(container, markupResourceStream, enforceReload); if (markup != null) { 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; } // In case the markup could not be loaded (without exception) then .. if (cacheKey != null) { removeMarkup(cacheKey); } return Markup.NO_MARKUP; }
private void removeMarkupWhereBaseMarkupIsNoLongerInTheCache() { // Repeat until all dependent resources have been removed (count == 0) int count = 1; while (count > 0) { // Reset prior to next round count = 0; // Iterate though all entries of the cache Iterator<Markup> iter = markupCache.getValues().iterator(); while (iter.hasNext()) { Markup markup = iter.next(); if ((markup != null) && (markup != Markup.NO_MARKUP)) { // Check if the markup associated with key has a base markup. And if yes, test // if that is cached. If the base markup has been removed, than remove the // derived markup as well. MarkupResourceStream resourceStream = markup.getMarkupResourceStream(); if (resourceStream != null) { resourceStream = resourceStream.getBaseMarkupResourceStream(); } // Is the base markup available in the cache? if ((resourceStream != null) && !isMarkupCached(resourceStream)) { iter.remove(); count++; if (log.isDebugEnabled()) { log.debug("Removed derived markup from cache: " + markup.getMarkupResourceStream()); } } } } } }
@Override public IFileSpec getEntry(ICacheKey<IFileSpec> key) { IFileSpec result = _cache.getEntry(key); if (null == result) { log(String.format("Cache miss for %d", key.getKey())); } else { log(String.format("Cache hit for %s: %d", result.getPreferredPathString(), key.getKey())); } return result; }
/** * 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. * * <p>More sophisticated implementations may call a container method to e.g. cache it per * container instance. * * @param locationString If {@code null} then 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, then * the markup from the cache is provided. */ protected Markup putIntoCache( final String locationString, final MarkupContainer container, Markup markup) { if (locationString != null) { if (markupCache.containsKey(locationString) == false) { // The default cache implementation is a ConcurrentHashMap. Thus neither the key nor // the value can be null. if (markup == null) { markup = Markup.NO_MARKUP; } 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; }
/** * Will be called if the markup was not in the cache yet and could not be found either. * * <p>Subclasses may change the default implementation. E.g. they might choose not to update the * cache to enforce reloading of any markup not found. This might be useful in very dynamic * environments. Additionally a non-caching IResourceStreamLocator should be used. * * @param cacheKey * @param container * @param markup Markup.NO_MARKUP * @return Same as parameter "markup" * @see * org.apache.wicket.settings.def.ResourceSettings#setResourceStreamLocator(org.apache.wicket.core.util.resource.locator.IResourceStreamLocator) */ protected Markup onMarkupNotFound( final String cacheKey, final MarkupContainer container, final Markup markup) { if (log.isDebugEnabled()) { log.debug("Markup not found: " + cacheKey); } // If cacheKey == null then caching is disabled for the component if (cacheKey != null) { // flag markup as non-existent markupKeyCache.put(cacheKey, cacheKey); putIntoCache(cacheKey, container, markup); } return markup; }
@Override public final int size() { return markupCache.size(); }
@Override public void shutdown() { markupCache.shutdown(); markupKeyCache.shutdown(); }
private void log(String message) { _logger.log(String.format("[%s]: %s", _cache.getCacheName(), message)); }
@Override public ICacheKey<IFileSpec> getCacheKey(IFileSpec entry) { return _cache.getCacheKey(entry); }
@Override public void flush() { _cache.flush(); log(String.format("Flushed")); }
@Override public void clear() { markupCache.clear(); markupKeyCache.clear(); }
@Override public String getCacheName() { return _cache.getCacheName(); }