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 25712) +++ 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/SxDocumentSource.java =================================================================== --- plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/SxDocumentSource.java (revision 25712) +++ 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 25712) +++ plugins/skinx/src/main/java/com/xpn/xwiki/web/sx/SxSource.java (working copy) @@ -20,6 +20,10 @@ */ package com.xpn.xwiki.web.sx; +import java.util.Date; + +import org.xwiki.bridge.DocumentName; + /** * Skin extension source. Can be a document, a resource file, or anything else. * @@ -35,25 +39,46 @@ { /** * Cache for a long time. - * @see AbstractSxAction#LONG_CACHE_DURATION + * @see AbstractSxSource#LONG_CACHE_DURATION */ - LONG, + LONG(30 * 24 * 3600 * 1000L), /** * Cache for a short time. - * @see AbstractSxAction#SHORT_CACHE_DURATION + * @see AbstractSxSource#SHORT_CACHE_DURATION */ - SHORT, + SHORT(1 * 24 * 3600 * 1000L), /** * 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, + DEFAULT(1 * 24 * 3600 * 1000L), /** Do not cache at all in server cache or in proxy/browser. */ - FORBID + FORBID(0x0000000000000000L); + + /** How many milliseconds this cache should last for. */ + private final long cacheDuration; + + /** + * Private Constructor. + * + * @param duration How many milliseconds this cache should last for. + */ + private CachePolicy(long duration) + { + cacheDuration = duration; + } + + /** + * @return The time when caches with this policy should expire if they start now + */ + public long getExpirationTime() + { + return new Date().getTime() + cacheDuration; + } } /** @@ -71,4 +96,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 caches 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 25712) +++ 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"; @@ -69,6 +61,9 @@ /** If the user specifies this url parameter equals false, we will send uncompressed script content. */ private static final String COMPRESS_SCRIPT_REQUEST_PARAMETER = "minify"; + /** Static instance of SxCache, lazy initialized because we need the context to get the desired capacity. */ + private static SxCache sxCache; + /** @return the logging object of the concrete subclass. */ protected abstract Log getLog(); @@ -84,7 +79,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 +93,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) && skipCompressionRequested(context)) { extensionContent = sxType.getCompressor().compress(extensionContent); } @@ -133,16 +121,31 @@ @Override public String render(XWikiContext context) throws XWikiException { - SxSource sxSource; + synchronized (this) { + if (sxCache == null) { + sxCache = SxCache.newInstance(context); + } + } + 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 = skipCompressionRequested(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); + sxSource = cacheSx; + } } try { @@ -161,4 +164,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 skipCompressionRequested(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/pom.xml =================================================================== --- plugins/skinx/pom.xml (revision 25712) +++ plugins/skinx/pom.xml (working copy) @@ -40,7 +40,7 @@ com.xpn.xwiki.platform xwiki-core - ${platform.core.version} + 2.2-SNAPSHOT provided Index: tools/xwiki-configuration-resources/src/main/resources/xwiki.cfg.vm =================================================================== --- tools/xwiki-configuration-resources/src/main/resources/xwiki.cfg.vm (revision 25712) +++ tools/xwiki-configuration-resources/src/main/resources/xwiki.cfg.vm (working copy) @@ -556,6 +556,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 #