Index: plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/AbstractSxSource.java =================================================================== --- plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/AbstractSxSource.java (revision 0) +++ plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/AbstractSxSource.java (revision 0) @@ -0,0 +1,103 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ +package com.xpn.xwiki.web.sx; + +import java.util.Date; + +import org.xwiki.bridge.DocumentName; + +/** + * Abstract Skin extension source. Can be a document, a resource file, or anything else. + * + * @version $Id$ + */ +public abstract class AbstractSxSource implements SxSource +{ + /** How many milliseconds a file should be cached for if it sets CachePolicy to LONG, hardcoded to 30 days. */ + private static final long LONG_CACHE_DURATION = 30 * 24 * 3600 * 1000L; + + /** How many milliseconds a file should be cached for if it sets CachePolicy to SHORT, hardcoded to 1 day. */ + private static final long SHORT_CACHE_DURATION = 1 * 24 * 3600 * 1000L; + + /** This is only used by the server cache, number of milliseconds to store if CachePolicy is DEFAULT (1 day). */ + private static final long DEFAULT_CACHE_DURATION = 1 * 24 * 3600 * 1000L; + + /** + * {@inheritDoc} + * + * @see SxSource#getLastModifiedDate() + */ + public long getLastModifiedDate() + { + // return 0, which will make the action not set any Last-Modified date in the response. + return 0; + } + + /** + * {@inheritDoc} + * + * @see SxSource#getDocumentName() + */ + public DocumentName getDocumentName() + { + return null; + } + + /** + * {@inheritDoc} + * + * @see SxSource#getCachePolicy() + */ + public abstract CachePolicy getCachePolicy(); + + /** + * {@inheritDoc} + * + * @see SxSource#getExpirationTime() + */ + public long getExpirationTime() + { + return getExpirationTime(getCachePolicy()); + } + + /** + * Get the point in time when a source should be refreshed based on it's CachePolicy. + * + * @param cachePolicy The {@link SxSource#CachePolicy} to determine expiration time from. + * @return Some number of milliseconds since the epoch when the given CachePolicy should expire. + */ + public static long getExpirationTime(CachePolicy cachePolicy) + { + if (cachePolicy == CachePolicy.LONG) { + // Cache for one month (30 days) + return new Date().getTime() + LONG_CACHE_DURATION; + } else if (cachePolicy == CachePolicy.SHORT) { + // Cache for one day + return new Date().getTime() + SHORT_CACHE_DURATION; + } else if (cachePolicy == CachePolicy.DEFAULT) { + // This only applies to SxCache + return new Date().getTime() + DEFAULT_CACHE_DURATION; + } + // otherwise assume FORBID + // return one millisecond ago, this shouldn't ever happen though. + return new Date().getTime() - 1; + } +} Property changes on: plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/AbstractSxSource.java ___________________________________________________________________ Name: svn:keywords + Author Id Revision HeadURL Name: svn:eol-style + native Index: plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/SxResourceSource.java =================================================================== --- plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/SxResourceSource.java (revision 24905) +++ plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/SxResourceSource.java (working copy) @@ -31,7 +31,7 @@ * @version $Id$ * @since 1.7M2 */ -public class SxResourceSource implements SxSource +public class SxResourceSource extends AbstractSxSource { /** * The full path of the resource to use as extension. Exemple: path/to/hello.js Index: plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/SxCacheableSource.java =================================================================== --- plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/SxCacheableSource.java (revision 0) +++ plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/SxCacheableSource.java (revision 0) @@ -0,0 +1,35 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ +package com.xpn.xwiki.web.sx; + +/** + * Cachable skin extension source. Can be a document, a resource file, or anything else. + * The content should be pre-loaded though. + * + * @version $Id$ + */ +public interface SxCacheableSource extends SxSource +{ + /** + * @return The extension type of this source object (eg: Javascript or CSS) + */ + Extension getExtension(); +} Property changes on: plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/SxCacheableSource.java ___________________________________________________________________ Name: svn:keywords + Author Id Revision HeadURL Name: svn:eol-style + native Index: plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/AbstractSxCacheableSource.java =================================================================== --- plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/AbstractSxCacheableSource.java (revision 0) +++ plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/AbstractSxCacheableSource.java (revision 0) @@ -0,0 +1,141 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ +package com.xpn.xwiki.web.sx; + +import org.xwiki.bridge.DocumentName; + +/** + * Abstract cacheable skin extension source. Classes which extend this need only be concerned with the storing and + * retreval of the source content. + * + * @version $Id$ + */ +public abstract class AbstractSxCacheableSource implements SxCacheableSource +{ + /** The type of extension (eg. Javascript or CSS) which this source is. */ + private final Extension extension; + + /** The last time the document containing this source was edited, 0 if not applicable. */ + private final long lastModifiedDate; + + /** How long the SxCache, or proxies or browsers should cache this source if at all. */ + private final CachePolicy cachePolicy; + + /** If this source came from a document, then name of that document, otherwise null. */ + private final DocumentName documentName; + + /** The time (milliseconds since the epoch) when all caches of this source should expire. */ + private final long expirationTime; + + /** + * The Constructor. + * + * @param source The source object which you want to cache. + * @param extension The type of extension which the source is (eg. Javascript or CSS) + */ + public AbstractSxCacheableSource(SxSource source, Extension extension) + { + this(extension, + source.getLastModifiedDate(), + source.getCachePolicy(), + source.getDocumentName(), + source.getExpirationTime()); + } + + /** + * The Manual Constructor. + * + * @param extension The type of extension which the source is (eg. Javascript or CSS) + * @param lastModifiedDate The date the source was last modified (0 if unknown) + * @param cachePolicy How long the SxCache, or proxies or browsers should cache this source if at all + * @param documentName The name of the document this source came, null if not applicable. + * @param expirationTime The time (milliseconds since the epoch) when all caches of this source should expire + */ + public AbstractSxCacheableSource(Extension extension, + long lastModifiedDate, + CachePolicy cachePolicy, + DocumentName documentName, + long expirationTime) + { + this.extension = extension; + this.lastModifiedDate = lastModifiedDate; + this.cachePolicy = cachePolicy; + this.documentName = documentName; + this.expirationTime = expirationTime; + } + + /** + * {@inheritDoc} + * + * @see SxCacheableSource#getExtension() + */ + public Extension getExtension() + { + return extension; + } + + /** + * {@inheritDoc} + * + * @see SxCacheableSource#getLastModifiedDate() + */ + public long getLastModifiedDate() + { + return lastModifiedDate; + } + + /** + * {@inheritDoc} + * + * @see SxCacheableSource#getCachePolicy() + */ + public CachePolicy getCachePolicy() + { + return cachePolicy; + } + + /** + * {@inheritDoc} + * + * @see SxCacheableSource#getDocumentName() + */ + public DocumentName getDocumentName() + { + return documentName; + } + + /** + * {@inheritDoc} + * + * @see SxCacheableSource#getExpirationTime() + */ + public long getExpirationTime() + { + return expirationTime; + } + + /** + * {@inheritDoc} + * + * @see SxCacheableSource#getContent() + */ + public abstract String getContent(); +} Property changes on: plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/AbstractSxCacheableSource.java ___________________________________________________________________ Name: svn:keywords + Author Id Revision HeadURL Name: svn:eol-style + native Index: plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/DefaultSxCacheableSource.java =================================================================== --- plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/DefaultSxCacheableSource.java (revision 0) +++ plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/DefaultSxCacheableSource.java (revision 0) @@ -0,0 +1,78 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ +package com.xpn.xwiki.web.sx; + +import org.xwiki.bridge.DocumentName; + +/** + * Default Skin extension source. Objects of this class can be put in the SxCache. + * also the content is pre-loaded and compressed. + * + * @version $Id$ + */ +public class DefaultSxCacheableSource extends AbstractSxCacheableSource +{ + /** A pre-loaded compressed copy of the code for this source. */ + private final String content; + + /** + * The Constructor. + * + * @param source The source which you want to make cacheable. + * @param extension The type of extension which this source is (eg: JsExtension.class for Javascript) + */ + public DefaultSxCacheableSource(SxSource source, Extension extension) + { + super(source, extension); + this.content = extension.getCompressor().compress(source.getContent()); + } + + /** + * The Manual Constructor. + * + * @param extension The type of extension which the source is (eg. Javascript or CSS) + * @param lastModifiedDate The date the source was last modified (0 if unknown) + * @param cachePolicy How long the SxCache, or proxies or browsers should cache this source if at all + * @param documentName The name of the document this source came, null if not applicable. + * @param expirationTime The time (milliseconds since the epoch) when all caches of this source should expire + * @param content The code which you want to cache (it will be compressed by this constructor) + */ + public DefaultSxCacheableSource(Extension extension, + long lastModifiedDate, + CachePolicy cachePolicy, + DocumentName documentName, + long expirationTime, + String content) + { + super(extension, lastModifiedDate, cachePolicy, documentName, expirationTime); + this.content = extension.getCompressor().compress(content); + } + + /** + * {@inheritDoc} + * + * @see AbstractSxCacheableSource#getContent() + */ + public String getContent() + { + return content; + } +} Property changes on: plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/DefaultSxCacheableSource.java ___________________________________________________________________ Name: svn:keywords + Author Id Revision HeadURL Name: svn:eol-style + native Index: plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/SxDocumentSource.java =================================================================== --- plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/SxDocumentSource.java (revision 24905) +++ plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/SxDocumentSource.java (working copy) @@ -27,6 +27,7 @@ import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.objects.BaseObject; +import org.xwiki.bridge.DocumentName; /** * Wiki Document source for Skin Extensions. This is the standard source for Skin Extensions, using an XWiki object of @@ -35,7 +36,7 @@ * @version $Id$ * @since 1.7M2 */ -public class SxDocumentSource implements SxSource +public class SxDocumentSource extends AbstractSxSource { /** The name of the property in the script extension object which contains the script content. */ private static final String CONTENT_PROPERTY_NAME = "code"; @@ -142,4 +143,14 @@ return this.document.getDate().getTime(); } + /** + * {@inheritDoc} + * + * @see SxSource#getDocumentName() + */ + public DocumentName getDocumentName() + { + return this.document.getDocumentName(); + } + } Index: plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/SxSource.java =================================================================== --- plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/SxSource.java (revision 24905) +++ plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/SxSource.java (working copy) @@ -20,6 +20,8 @@ */ package com.xpn.xwiki.web.sx; +import org.xwiki.bridge.DocumentName; + /** * Skin extension source. Can be a document, a resource file, or anything else. * @@ -35,20 +37,20 @@ { /** * Cache for a long time. - * @see AbstractSxAction#LONG_CACHE_DURATION + * @see AbstractSxSource#LONG_CACHE_DURATION */ LONG, /** * Cache for a short time. - * @see AbstractSxAction#SHORT_CACHE_DURATION + * @see AbstractSxSource#SHORT_CACHE_DURATION */ SHORT, /** * Cache for the proxy/browser's default time, It will be held in the - * server cache an amount of time specified in AbstractSxAction. - * @see AbstractSxAction.DEFAULT_CACHE_DURATION + * server cache an amount of time specified in AbstractSxSource. + * @see AbstractSxSource.DEFAULT_CACHE_DURATION */ DEFAULT, @@ -71,4 +73,13 @@ */ CachePolicy getCachePolicy(); + /** + * @return The DocumentName of the document from which this script was taken or null of not applicable. + */ + DocumentName getDocumentName(); + + /** + * @return The time when the cache of this source should expire. + */ + long getExpirationTime(); } Index: plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/AbstractSxAction.java =================================================================== --- plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/AbstractSxAction.java (revision 24905) +++ plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/AbstractSxAction.java (working copy) @@ -21,7 +21,6 @@ package com.xpn.xwiki.web.sx; import java.io.IOException; -import java.util.Date; import javax.servlet.http.HttpServletResponse; @@ -32,7 +31,6 @@ import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.web.XWikiAction; -import com.xpn.xwiki.web.XWikiRequest; import com.xpn.xwiki.web.XWikiResponse; import com.xpn.xwiki.web.sx.SxSource.CachePolicy; @@ -45,12 +43,6 @@ */ public abstract class AbstractSxAction extends XWikiAction { - /** How many milliseconds a file should be cached for if it sets CachePolicy to LONG, hardcoded to 30 days. */ - private static final long LONG_CACHE_DURATION = 30 * 24 * 3600 * 1000L; - - /** How many milliseconds a file should be cached for if it sets CachePolicy to SHORT, hardcoded to 1 day. */ - private static final long SHORT_CACHE_DURATION = 1 * 24 * 3600 * 1000L; - /** What http header parameter is used to specify when a file was last modified. */ private static final String LAST_MODIFIED_HEADER = "Last-Modified"; @@ -84,7 +76,6 @@ public void renderExtension(SxSource sxSource, Extension sxType, XWikiContext context) throws XWikiException { - XWikiRequest request = context.getRequest(); XWikiResponse response = context.getResponse(); String extensionContent = sxSource.getContent(); @@ -99,20 +90,14 @@ if (cachePolicy != CachePolicy.FORBID) { response.setHeader(CACHE_CONTROL_HEADER, "public"); - } - if (cachePolicy == CachePolicy.LONG) { - // Cache for one month (30 days) - response.setDateHeader(CACHE_EXPIRES_HEADER, (new Date()).getTime() + LONG_CACHE_DURATION); - } else if (cachePolicy == CachePolicy.SHORT) { - // Cache for one day - response.setDateHeader(CACHE_EXPIRES_HEADER, (new Date()).getTime() + SHORT_CACHE_DURATION); - } else if (cachePolicy == CachePolicy.FORBID) { + if (cachePolicy != CachePolicy.DEFAULT) { + response.setHeader(CACHE_EXPIRES_HEADER, (Long.toString(sxSource.getExpirationTime()))); + } + } else { response.setHeader(CACHE_CONTROL_HEADER, "no-cache, no-store, must-revalidate"); } - if (BooleanUtils.toBoolean(StringUtils.defaultIfEmpty( - request.get(COMPRESS_SCRIPT_REQUEST_PARAMETER), "true"))) - { + if (!(sxSource instanceof SxCacheableSource) && userWantsUncompressedScript(context)) { extensionContent = sxType.getCompressor().compress(extensionContent); } @@ -133,16 +118,26 @@ @Override public String render(XWikiContext context) throws XWikiException { - SxSource sxSource; + SxSource sxSource = SxCache.get(context.getURL().toString()); - if (context.getRequest().getParameter(JAR_RESOURCE_REQUEST_PARAMETER) != null) { - sxSource = new SxResourceSource(context.getRequest().getParameter(JAR_RESOURCE_REQUEST_PARAMETER)); - } else { - if (context.getDoc().isNew()) { - context.getResponse().setStatus(HttpServletResponse.SC_NOT_FOUND); - return "docdoesnotexist"; + boolean userWantsUncompressed = userWantsUncompressedScript(context); + if (sxSource == null || userWantsUncompressed) { + if (context.getRequest().getParameter(JAR_RESOURCE_REQUEST_PARAMETER) != null) { + sxSource = new SxResourceSource(context.getRequest().getParameter(JAR_RESOURCE_REQUEST_PARAMETER)); + } else { + if (context.getDoc().isNew()) { + context.getResponse().setStatus(HttpServletResponse.SC_NOT_FOUND); + return "docdoesnotexist"; + } + sxSource = new SxDocumentSource(context, getExtensionType()); } - sxSource = new SxDocumentSource(context, getExtensionType()); + // There is no point in caching an uncompressed script since the cache is always compressed + // so if the user did not want an uncompressed script, we assume the cache needed updating. + if (!userWantsUncompressed) { + SxCacheableSource cacheSx = new DefaultSxCacheableSource(sxSource, getExtensionType()); + SxCache.put(context.getURL().toString(), cacheSx, context); + sxSource = cacheSx; + } } try { @@ -161,4 +156,22 @@ */ public abstract Extension getExtensionType(); + /** + * Get Whether or not the user has requested an uncompressed version of the script. + * + * @param context The XWikiContext + * @return does the user want an uncompressed (origional format) script? + */ + private boolean userWantsUncompressedScript(XWikiContext context) + { + if (!BooleanUtils.toBoolean( + StringUtils.defaultIfEmpty(context.getRequest().get(COMPRESS_SCRIPT_REQUEST_PARAMETER), "true"))) + { + if (getLog().isDebugEnabled()) { + getLog().debug("The user has requested an uncompressed version of a script"); + } + return true; + } + return false; + } } Index: plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/SxCache.java =================================================================== --- plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/SxCache.java (revision 0) +++ plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/SxCache.java (revision 0) @@ -0,0 +1,261 @@ +/* + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + * + */ +package com.xpn.xwiki.web.sx; + +import java.util.HashMap; +import java.util.Map; +import java.util.HashSet; +import java.util.Set; +import java.util.Date; +import java.util.List; +import java.util.ArrayList; + +import com.xpn.xwiki.doc.XWikiDocument; +import com.xpn.xwiki.XWikiContext; +import com.xpn.xwiki.web.Utils; +import org.xwiki.bridge.DocumentName; +import org.xwiki.observation.event.DocumentDeleteEvent; +import org.xwiki.observation.event.DocumentUpdateEvent; +import org.xwiki.observation.event.Event; +import org.xwiki.observation.EventListener; +import org.xwiki.observation.ObservationManager; +import org.xwiki.cache.CacheManager; +import org.xwiki.cache.Cache; +import org.xwiki.cache.config.CacheConfiguration; +import org.xwiki.cache.eviction.LRUEvictionConfiguration; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * SxCache caches scripts to decrease load from minifying them. + * + * @version $Id$ + */ +public final class SxCache implements EventListener +{ + /** The name of this class used for a prefix for configuration parameters and for the name of the cache object. */ + private static final String CACHE_NAME = "xwiki.plugin.skinx.cache"; + + /** + * The parameter to be found in xwiki.cfg which defines the number of scripts which can be stored in the cache. + * appended to the content of CACHE_NAME + */ + private static final String CACHE_CAPACITY_CFG_PARAM = ".capacity"; + + /** The default capacity for the cache in case parameter is specified. */ + private static final int DEFAULT_CACHE_CAPACITY = 50; + + /** Log object to log messages in this class. */ + private static final Log LOG = LogFactory.getLog(SxCache.class); + + /** The cache object for the SxCache. */ + private static Cache cache; + + /** Map of url by DocumentName for updating cache on document modification. */ + private static Map> urlsByDocumentName = + new HashMap>(); + + /** + * Private constructor. + * The useful methods are static so it makes no sense to create an instance. + * An instance is created for use in the ObservationManager + * Scripts are cached by their URL so it is hard to imagine any need for multiple caches. + */ + private SxCache() { } + + /** + * Gets a script based on it's url, if the script is not there or has expired, returns null. + * + * @param url String representation of the url of the script to get. + * @return SxCacheableSource object + */ + public static SxCacheableSource get(String url) + { + if (cache == null) { return null; } + SxCacheableSource source = cache.get(url); + // Find out if there is a source in the cache + if (source != null) { + // Now make sure the script hasn't passed it's expiration date. + if (source.getExpirationTime() < new Date().getTime()) { + if (LOG.isDebugEnabled()) { + LOG.debug(url + " Has expired and will be removed from the cache."); + } + remove(url); + return null; + } + if (LOG.isDebugEnabled()) { + LOG.debug(url + " was loaded from SxCache."); + } + return source; + } + if (LOG.isDebugEnabled()) { + LOG.debug(url + " was not found in the SxCache."); + } + return null; + } + + /** + * Puts a script into the cache. + * + * @param url A string representation of the url to call the script + * @param source An SxCacheableSource object made from the script + * @param context The XWikiContext used to get the configuration to determine the cache capacity for initialization + */ + public static void put(String url, SxCacheableSource source, XWikiContext context) + { + // If the CachePolicy is FORBID, we shouldn't cache it. + if (source.getCachePolicy() == SxCacheableSource.CachePolicy.FORBID) { + cache.remove(url); + if (LOG.isDebugEnabled()) { + LOG.debug(url + " was not cached by SxCache due to FORBID CachePolicy"); + } + return; + } + // Put it in the cache. + // If this is the first call of the day, the cache must be initialized + // and this class must be registered with ObservationManager. + initIfNecessary(context); + cache.set(url, source); + if (LOG.isDebugEnabled()) { + LOG.debug(url + " was put in SxCache."); + } + // Cache the DocumentName so the cached source can be invalidated if the document changes. + // I wish I could think of a better way to do this. + if (source.getDocumentName() != null) { + // This must be synchronized to insure no document script leaks through without being listed. + synchronized (urlsByDocumentName) { + Set urls = urlsByDocumentName.get(source.getDocumentName()); + if (urls == null) { + urlsByDocumentName.put(source.getDocumentName(), new HashSet()); + urls = urlsByDocumentName.get(source.getDocumentName()); + } + urls.add(url); + } + } + } + + /** + * Removes an entry from the cache. + * + * @param url A String representation of the url of the entry to be removed. + */ + public static void remove(String url) + { + if (cache != null) { + cache.remove(url); + LOG.debug(url + " was removed from SxCache"); + } else { + LOG.debug("could not remove " + url + " SxCache not initialized yet."); + } + } + + /** + * Initializes the cache if it is not already initialized. + * + * @param context The XWikiContext used for getting the cache capacity specification from the configuration. + */ + private static synchronized void initIfNecessary(XWikiContext context) + { + if (cache != null) { + return; + } + int iCapacity = DEFAULT_CACHE_CAPACITY; + String capacity = context.getWiki().Param(CACHE_NAME + CACHE_CAPACITY_CFG_PARAM); + if (capacity != null) { + try { + iCapacity = Integer.parseInt(capacity); + LOG.debug("SxCache initialized with capacity of " + iCapacity + " defined by xwiki.cfg"); + } catch (NumberFormatException e) { + LOG.info("Could not understand value for configuration parameter: " + + CACHE_NAME + CACHE_CAPACITY_CFG_PARAM + " expecting an integer."); + } + } else { + LOG.debug("SxCache was initialized with the default capacity (" + DEFAULT_CACHE_CAPACITY + ")"); + } + try { + CacheManager cacheManager = Utils.getComponent(CacheManager.class); + CacheConfiguration configuration = new CacheConfiguration(); + configuration.setConfigurationId(CACHE_NAME); + LRUEvictionConfiguration lru = new LRUEvictionConfiguration(); + lru.setMaxEntries(iCapacity); + configuration.put(LRUEvictionConfiguration.CONFIGURATIONID, lru); + + cache = cacheManager.createNewLocalCache(configuration); + } catch (Exception e) { + LOG.info("Failed to initialize the SxCache."); + return; + } + Utils.getComponent(ObservationManager.class).addListener(new SxCache()); + } + + // =============== Now the stuff for ObservationManager =============== + + /** + * {@inheritDoc} + * + * @see EventListener#getName() + */ + public String getName() + { + return CACHE_NAME; + } + + /** + * {@inheritDoc} + * + * @see EventListener#getEvents() + */ + public List getEvents() + { + List events = new ArrayList(); + events.add(new DocumentUpdateEvent()); + events.add(new DocumentDeleteEvent()); + return events; + } + + /** + * {@inheritDoc} + * + * @see EventListener#onEvent(Event, Object, Object) + */ + public void onEvent(Event event, Object source, Object data) + { + DocumentName docName = ((XWikiDocument) source).getDocumentName(); + // Remove all urls which point to the document from the cache + synchronized (urlsByDocumentName) { + if (urlsByDocumentName.containsKey(docName)) { + for (String url : urlsByDocumentName.get(docName)) { + remove(url); + if (LOG.isDebugEnabled()) { + if (event instanceof DocumentUpdateEvent) { + LOG.debug(url + " was removed from SxCache because containing document was updated."); + } else { + LOG.debug(url + " was removed from SxCache because containing document was deleted."); + } + } + } + } + // Remove the DocumentName from the map. If it was deleted, it is cleaned, otherwise it will be replaced. + urlsByDocumentName.remove(docName); + } + } +} Property changes on: plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/SxCache.java ___________________________________________________________________ Name: svn:keywords + Author Id Revision HeadURL Name: svn:eol-style + native Index: plugins/skinx/pom.xml =================================================================== --- plugins/skinx/pom.xml (revision 24905) +++ plugins/skinx/pom.xml (working copy) @@ -40,7 +40,8 @@ com.xpn.xwiki.platform xwiki-core - ${platform.core.version} + 2.1-SNAPSHOT + provided Index: tools/xwiki-configuration-resources/src/main/resources/xwiki.cfg.vm =================================================================== --- tools/xwiki-configuration-resources/src/main/resources/xwiki.cfg.vm (revision 24905) +++ tools/xwiki-configuration-resources/src/main/resources/xwiki.cfg.vm (working copy) @@ -552,6 +552,14 @@ #-# Default: 0 # xwiki.plugin.activitystream.daystokeepevents=0 +#-# Skin Extension plugin. +#-# The Skinx plugin provides a way to get scripts such as CSS or Javascript to the browser from objects attached to +#-# or from resources within JAR files. The Skinx plugin impliments a cache which stores the scripts in minified form. +#-# +#-# The capacity (number of scripts which may be held) in the Skinx cache (Default is 50). +# xwiki.plugin.skinx.cache.capacity=50 + + #--------------------------------------- # Misc #