/** * 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; }
/** * @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); }
/** * 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; }