From a8d042b241c09f8f4cb44224e6e6617e902a30e1 Mon Sep 17 00:00:00 2001 From: Guillaume Delhumeau <guillaume.delhumeau@xwiki.com> Date: Thu, 23 Oct 2014 13:28:30 +0200 Subject: [PATCH] XWIKI-10776: The cache of the LESSCompiler is badly designed. --- xwiki-platform-core/pom.xml | 4 + .../java/org/xwiki/lesscss/LESSCache.java | 27 +++-- .../xwiki/lesscss/internal/AbstractCache.java | 100 ++++++++++++---- .../internal/AbstractCachedCompiler.java | 46 ++------ ...eListener.java => ColorThemeListener.java} | 35 +++--- .../internal/CurrentColorThemeGetter.java | 38 +++++++ .../DefaultCurrentColorThemeGetter.java | 79 +++++++++++++ .../internal/DefaultLESSSkinFileCompiler.java | 4 + .../internal/LESSExportActionListener.java | 11 +- .../main/resources/META-INF/components.txt | 5 +- ...rTest.java => ColorThemeListenerTest.java} | 58 ++++------ .../DefaultCurrentColorThemeGetterTest.java | 107 ++++++++++++++++++ .../DefaultLESSSkinFileCacheTest.java | 73 +++++++++--- .../DefaultLESSSkinFileCompilerTest.java | 96 ++++------------ .../LESSExportActionListenerTest.java | 9 +- .../lesscss/LessCompilerScriptService.java | 38 ++++++- .../LessCompilerScriptServiceTest.java | 71 ++++++++++-- 17 files changed, 576 insertions(+), 225 deletions(-) rename xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/{SkinAndColorThemeListener.java => ColorThemeListener.java} (76%) create mode 100644 xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/CurrentColorThemeGetter.java create mode 100644 xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/DefaultCurrentColorThemeGetter.java rename xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/test/java/org/xwiki/lesscss/internal/{SkinAndColorThemeListenerTest.java => ColorThemeListenerTest.java} (77%) create mode 100644 xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/test/java/org/xwiki/lesscss/internal/DefaultCurrentColorThemeGetterTest.java diff --git a/xwiki-platform-core/pom.xml b/xwiki-platform-core/pom.xml index 8af028369c3..1f988f5e9f2 100644 --- a/xwiki-platform-core/pom.xml +++ b/xwiki-platform-core/pom.xml @@ -364,6 +364,10 @@ <exclude>**/internal/**</exclude> <exclude>**/test/**</exclude> <!-- Remove the following excludes after we release the current version as final --> + <!-- Changed the LESSCache interface and the LESS Script Service because the cache was poorly designed + (see: http://jira.xwiki.org/browse/XWIKI-10776). --> + <exclude>**/LESSCache*</exclude> + <exclude>**/LessCompilerScriptService*</exclude> </excludes> </configuration> </plugin> diff --git a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-api/src/main/java/org/xwiki/lesscss/LESSCache.java b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-api/src/main/java/org/xwiki/lesscss/LESSCache.java index d5ce683ba0d..361832aae0e 100644 --- a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-api/src/main/java/org/xwiki/lesscss/LESSCache.java +++ b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-api/src/main/java/org/xwiki/lesscss/LESSCache.java @@ -32,23 +32,25 @@ public interface LESSCache<T> /** * Get an object from the name of the LESS source, the wiki ID, the skin and the name of the color theme. * @param fileName name of the LESS source - * @param wikiId id of the wiki * @param skin name of the skin * @param colorTheme name of the color theme * @return the corresponding CSS + * + * @since 6.3M2 */ - T get(String fileName, String wikiId, String skin, String colorTheme); + T get(String fileName, String skin, String colorTheme); /** * Add an object in the cache. * * @param fileName name of the LESS source - * @param wikiId if of the wiki * @param fileSystemSkin name of the skin * @param colorThemeName name of the color theme * @param object the object to cache + * + * @since 6.3M2 */ - void set(String fileName, String wikiId, String fileSystemSkin, String colorThemeName, T object); + void set(String fileName, String fileSystemSkin, String colorThemeName, T object); /** * Clear the cache. @@ -56,9 +58,20 @@ public interface LESSCache<T> void clear(); /** - * Clear all the cached files related to a wiki. + * Clear all the cached files related to a skin. + * + * @param fileSystemSkin name of the filesystem skin + * + * @since 6.3M2 + */ + void clearFromFileSystemSkin(String fileSystemSkin); + + /** + * Clear all the cached files related to a color theme. + * + * @param colorTheme name of the color theme * - * @param wikiId id of the wiki + * @since 6.3M2 */ - void clear(String wikiId); + void clearFromColorTheme(String colorTheme); } diff --git a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/AbstractCache.java b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/AbstractCache.java index 5c86393b92e..71883335e76 100644 --- a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/AbstractCache.java +++ b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/AbstractCache.java @@ -29,6 +29,11 @@ import org.xwiki.cache.Cache; import org.xwiki.cache.CacheManager; import org.xwiki.lesscss.LESSCache; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.model.reference.DocumentReferenceResolver; +import org.xwiki.model.reference.EntityReferenceSerializer; +import org.xwiki.model.reference.WikiReference; +import org.xwiki.wiki.descriptor.WikiDescriptorManager; /** * Default and abstract implementation of {@link org.xwiki.lesscss.LESSCache}. @@ -51,29 +56,62 @@ public abstract class AbstractCache<T> implements LESSCache<T> protected Cache<T> cache; /** - * This map stores the list of the cached files keys corresponding to a wiki. + * This map stores the list of the cached files keys corresponding to a skin, in order to clear the corresponding + * cache when a skin is saved. */ - private Map<String, List<String>> cachedFilesKeysMap = new HashMap<>(); + private Map<String, List<String>> cachedFilesKeysMapPerSkin = new HashMap<>(); + + /** + * This map stores the list of the cached files keys corresponding to a color theme, in order to clear the + * corresponding cache when a color theme is saved. + */ + private Map<String, List<String>> cachedFilesKeysMapPerColorTheme = new HashMap<>(); + + @Inject + private DocumentReferenceResolver<String> documentReferenceResolver; + + @Inject + private EntityReferenceSerializer<String> entityReferenceSerializer; + + @Inject + private WikiDescriptorManager wikiDescriptorManager; @Override - public T get(String fileName, String wikiId, String fileSystemSkin, String colorTheme) + public T get(String fileName, String fileSystemSkin, String colorTheme) { - return cache.get(getCacheKey(fileName, wikiId, fileSystemSkin, colorTheme)); + return cache.get(getCacheKey(fileName, fileSystemSkin, getColorThemeFullName(colorTheme))); } @Override - public void set(String fileName, String wikiId, String fileSystemSkin, String colorTheme, T content) + public void set(String fileName, String fileSystemSkin, String colorTheme, T content) { + // Get the fullname of the color theme + String colorThemeFullName = getColorThemeFullName(colorTheme); + // Store the content in the cache - String cacheKey = getCacheKey(fileName, wikiId, fileSystemSkin, colorTheme); + String cacheKey = getCacheKey(fileName, fileSystemSkin, colorThemeFullName); cache.set(cacheKey, content); - // Add the new key to cachedFilesKeysMap. - List<String> cachedFilesKeys = cachedFilesKeysMap.get(wikiId); + // Add the new key to maps + registerCacheKey(cachedFilesKeysMapPerSkin, cacheKey, fileSystemSkin); + registerCacheKey(cachedFilesKeysMapPerColorTheme, cacheKey, colorTheme); + } + + /** + * Add the cache key in the specified map (cachedFilesKeysMapPerSkin or cachedFilesKeysMapPerColorTheme), to be + * able to clear the cache when one skin or one color theme is modified. + * + * @param cachedFilesKeysMap could be cachedFilesKeysMapPerSkin or cachedFilesKeysMapPerColorTheme + * @param cacheKey the cache key to register + * @param name name of the skin or of the color theme + */ + private void registerCacheKey(Map<String, List<String>> cachedFilesKeysMap, String cacheKey, String name) + { + List<String> cachedFilesKeys = cachedFilesKeysMap.get(name); if (cachedFilesKeys == null) { - // if the list of cached files keys corresponding to the wiki does not exist, we create it + // if the list of cached files keys corresponding to the skin/colortheme name does not exist, we create it cachedFilesKeys = new ArrayList<>(); - cachedFilesKeysMap.put(wikiId, cachedFilesKeys); + cachedFilesKeysMap.put(name, cachedFilesKeys); } if (!cachedFilesKeys.contains(cacheKey)) { cachedFilesKeys.add(cacheKey); @@ -84,14 +122,14 @@ public void set(String fileName, String wikiId, String fileSystemSkin, String co public void clear() { cache.removeAll(); - cachedFilesKeysMap.clear(); + cachedFilesKeysMapPerSkin.clear(); + cachedFilesKeysMapPerColorTheme.clear(); } - @Override - public void clear(String wikiId) + private void clearFromCriteria(Map<String, List<String>> cachedFilesKeysMap, String criteria) { - // Get the list of cached files keys corresponding to the wiki - List<String> cachedFilesKeys = cachedFilesKeysMap.get(wikiId); + // Get the list of cached files keys corresponding to the criteria + List<String> cachedFilesKeys = cachedFilesKeysMap.get(criteria); if (cachedFilesKeys == null) { return; } @@ -99,13 +137,35 @@ public void clear(String wikiId) for (String cachedFileKey : cachedFilesKeys) { cache.remove(cachedFileKey); } - // Remove the list of cached keys corresponding to the wiki - cachedFilesKeysMap.remove(wikiId); + // Remove the list of cached keys corresponding to the criteria + cachedFilesKeysMap.remove(criteria); + } + + @Override + public void clearFromFileSystemSkin(String fileSystemSkin) + { + clearFromCriteria(cachedFilesKeysMapPerSkin, fileSystemSkin); + } + + @Override + public void clearFromColorTheme(String colorTheme) + { + clearFromCriteria(cachedFilesKeysMapPerColorTheme, colorTheme); + } + + private String getColorThemeFullName(String colorTheme) + { + // Current Wiki Reference + WikiReference currentWikiRef = new WikiReference(wikiDescriptorManager.getCurrentWikiId()); + // Get the full reference of the color theme + DocumentReference colorThemeRef = documentReferenceResolver.resolve(colorTheme, currentWikiRef); + // Return the serialized reference + return entityReferenceSerializer.serialize(colorThemeRef); } - private String getCacheKey(String fileName, String wikiId, String skin, String colorTheme) + private String getCacheKey(String fileName, String skin, String colorThemeFullName) { - return wikiId.length() + wikiId + CACHE_KEY_SEPARATOR + skin.length() + skin + CACHE_KEY_SEPARATOR - + colorTheme.length() + colorTheme + CACHE_KEY_SEPARATOR + fileName.length() + fileName; + return skin.length() + skin + CACHE_KEY_SEPARATOR + colorThemeFullName.length() + colorThemeFullName + + CACHE_KEY_SEPARATOR + fileName.length() + fileName; } } diff --git a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/AbstractCachedCompiler.java b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/AbstractCachedCompiler.java index 7884f2fe4e2..6717009c739 100644 --- a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/AbstractCachedCompiler.java +++ b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/AbstractCachedCompiler.java @@ -27,14 +27,8 @@ import org.xwiki.lesscss.LESSCache; import org.xwiki.lesscss.LESSCompilerException; -import org.xwiki.model.reference.DocumentReference; -import org.xwiki.model.reference.DocumentReferenceResolver; -import org.xwiki.model.reference.EntityReferenceSerializer; -import org.xwiki.model.reference.WikiReference; -import org.xwiki.wiki.descriptor.WikiDescriptorManager; import com.xpn.xwiki.XWikiContext; -import com.xpn.xwiki.web.XWikiRequest; /** * Implements a cache system to prevent the compiler to be called too often. @@ -52,13 +46,7 @@ public abstract class AbstractCachedCompiler<T> protected Provider<XWikiContext> xcontextProvider; @Inject - protected WikiDescriptorManager wikiDescriptorManager; - - @Inject - protected DocumentReferenceResolver<String> referenceResolver; - - @Inject - protected EntityReferenceSerializer<String> referenceSerializer; + private CurrentColorThemeGetter currentColorThemeGetter; private Map<String, Object> mutexList = new HashMap<>(); @@ -88,29 +76,15 @@ public T compileFromSkinFile(String fileName, String skin, boolean force) throws { T result; - // Get information about the context - String wikiId = wikiDescriptorManager.getCurrentWikiId(); - XWikiContext context = xcontextProvider.get(); - XWikiRequest request = context.getRequest(); - - // Getting the full name representation of colorTheme - DocumentReference colorThemeReference = referenceResolver.resolve(request.getParameter("colorTheme"), - new WikiReference(wikiId)); - String colorTheme = referenceSerializer.serialize(colorThemeReference); - - // Check that the color theme exists, to avoid a DOS if some user tries to compile a skin file - // with random colorTheme names - if (!context.getWiki().exists(colorThemeReference, context)) { - colorTheme = "default"; - } + String colorTheme = currentColorThemeGetter.getCurrentColorTheme("default"); - // Only one computation is allowed in the same time on a wiki, then the waiting threads will be able to use - // the last result stored in the cache - synchronized (getMutex(wikiId)) { + // Only one computation is allowed in the same time per color theme, then the waiting threads will be able to + // use the last result stored in the cache + synchronized (getMutex(colorTheme)) { // Check if the result is in the cache if (!force) { - result = cache.get(fileName, wikiId, skin, colorTheme); + result = cache.get(fileName, skin, colorTheme); if (result != null) { return result; } @@ -118,7 +92,7 @@ public T compileFromSkinFile(String fileName, String skin, boolean force) throws // Either the result was in the cache or the force flag is set to true, we need to compile result = compile(fileName, skin, force); - cache.set(fileName, wikiId, skin, colorTheme, result); + cache.set(fileName, skin, colorTheme, result); } @@ -127,12 +101,12 @@ public T compileFromSkinFile(String fileName, String skin, boolean force) throws protected abstract T compile(String fileName, String skin, boolean force) throws LESSCompilerException; - private synchronized Object getMutex(String wikiId) + private synchronized Object getMutex(String colorThemeFullName) { - Object mutex = mutexList.get(wikiId); + Object mutex = mutexList.get(colorThemeFullName); if (mutex == null) { mutex = new Object(); - mutexList.put(wikiId, mutex); + mutexList.put(colorThemeFullName, mutex); } return mutex; } diff --git a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/SkinAndColorThemeListener.java b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/ColorThemeListener.java similarity index 76% rename from xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/SkinAndColorThemeListener.java rename to xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/ColorThemeListener.java index 3bd77a8ee24..eeae9e146df 100644 --- a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/SkinAndColorThemeListener.java +++ b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/ColorThemeListener.java @@ -32,7 +32,7 @@ import org.xwiki.component.annotation.Component; import org.xwiki.lesscss.ColorThemeCache; import org.xwiki.lesscss.LESSSkinFileCache; -import org.xwiki.model.reference.DocumentReference; +import org.xwiki.model.reference.EntityReferenceSerializer; import org.xwiki.model.reference.LocalDocumentReference; import org.xwiki.observation.EventListener; import org.xwiki.observation.event.Event; @@ -41,15 +41,15 @@ import com.xpn.xwiki.objects.BaseObject; /** - * Listener that clears the cache of compiled LESS Skin file when a skin or a color theme is changed. + * Listener that clears the cache of compiled LESS Skin file when a color theme is changed. * - * @since 6.1M2 + * @since 6.3M2 * @version $Id$ */ @Component -@Named("lessSkinColorTheme") +@Named("lessColorTheme") @Singleton -public class SkinAndColorThemeListener implements EventListener +public class ColorThemeListener implements EventListener { private static final LocalDocumentReference COLOR_THEME_CLASS = new LocalDocumentReference("ColorThemes", "ColorThemeClass"); @@ -57,14 +57,15 @@ public class SkinAndColorThemeListener implements EventListener private static final LocalDocumentReference FLAMINGO_THEME_CLASS = new LocalDocumentReference("FlamingoThemesCode", "ThemeClass"); - private static final LocalDocumentReference SKIN_CLASS = new LocalDocumentReference("XWiki", "XWikiSkins"); - @Inject private LESSSkinFileCache lessSkinFileCache; @Inject private ColorThemeCache colorThemeCache; + @Inject + private EntityReferenceSerializer<String> entityReferenceSerializer; + @Override public String getName() { @@ -87,29 +88,21 @@ public void onEvent(Event event, Object source, Object data) List<BaseObject> flamingoThemeObjects = document.getXObjects(FLAMINGO_THEME_CLASS); if (flamingoThemeObjects != null && !flamingoThemeObjects.isEmpty()) { - clearCache(document); + clearCacheFromColorTheme(document); return; } List<BaseObject> colorThemeObjects = document.getXObjects(COLOR_THEME_CLASS); if (colorThemeObjects != null && !colorThemeObjects.isEmpty()) { - clearCache(document); + clearCacheFromColorTheme(document); return; } - - List<BaseObject> skinObjects = document.getXObjects(SKIN_CLASS); - if (skinObjects != null && !skinObjects.isEmpty()) { - clearCache(document); - } } - private void clearCache(XWikiDocument document) + private void clearCacheFromColorTheme(XWikiDocument document) { - DocumentReference documentReference = document.getDocumentReference(); - - // Clear the cache for the specified wiki and color theme - String wiki = documentReference.getWikiReference().getName(); - lessSkinFileCache.clear(wiki); - colorThemeCache.clear(wiki); + String fullName = entityReferenceSerializer.serialize(document.getDocumentReference()); + lessSkinFileCache.clearFromColorTheme(fullName); + colorThemeCache.clearFromColorTheme(fullName); } } diff --git a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/CurrentColorThemeGetter.java b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/CurrentColorThemeGetter.java new file mode 100644 index 00000000000..9ead290cc94 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/CurrentColorThemeGetter.java @@ -0,0 +1,38 @@ +/* + * 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 org.xwiki.lesscss.internal; + +import org.xwiki.component.annotation.Role; + +/** + * Component to get the current color theme set by the request. + * + * @since 6.3M2 + * @version $Id$ + */ +@Role +public interface CurrentColorThemeGetter +{ + /** + * @param fallbackValue value to return if the current color theme is invalid + * @return the full name of the current color theme or fallbackValue if the current color theme is invalid + */ + String getCurrentColorTheme(String fallbackValue); +} diff --git a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/DefaultCurrentColorThemeGetter.java b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/DefaultCurrentColorThemeGetter.java new file mode 100644 index 00000000000..8468e1d7ac2 --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/DefaultCurrentColorThemeGetter.java @@ -0,0 +1,79 @@ +/* + * 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 org.xwiki.lesscss.internal; + +import javax.inject.Inject; +import javax.inject.Provider; + +import org.xwiki.component.annotation.Component; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.model.reference.DocumentReferenceResolver; +import org.xwiki.model.reference.EntityReferenceSerializer; +import org.xwiki.model.reference.WikiReference; +import org.xwiki.wiki.descriptor.WikiDescriptorManager; + +import com.xpn.xwiki.XWikiContext; +import com.xpn.xwiki.web.XWikiRequest; + +/** + * Default implementation for {@link org.xwiki.lesscss.internal.CurrentColorThemeGetter}. + * + * @since 6.3M2 + * @version $Id$ + */ +@Component +public class DefaultCurrentColorThemeGetter implements CurrentColorThemeGetter +{ + @Inject + private DocumentReferenceResolver<String> documentReferenceResolver; + + @Inject + private EntityReferenceSerializer<String> entityReferenceSerializer; + + @Inject + private Provider<XWikiContext> xcontextProvider; + + @Inject + private WikiDescriptorManager wikiDescriptorManager; + + @Override + public String getCurrentColorTheme(String fallbackValue) + { + // Get information about the context + String wikiId = wikiDescriptorManager.getCurrentWikiId(); + XWikiContext context = xcontextProvider.get(); + XWikiRequest request = context.getRequest(); + + // Get the current color theme + // Getting the full name representation of colorTheme + DocumentReference colorThemeReference = documentReferenceResolver.resolve(request.getParameter("colorTheme"), + new WikiReference(wikiId)); + String colorTheme = entityReferenceSerializer.serialize(colorThemeReference); + + // Check that the color theme exists, to avoid a DOS if some user tries to compile a skin file + // with random colorTheme names + if (!context.getWiki().exists(colorThemeReference, context)) { + colorTheme = fallbackValue; + } + + return colorTheme; + } + +} diff --git a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/DefaultLESSSkinFileCompiler.java b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/DefaultLESSSkinFileCompiler.java index 9d3c5c23fb4..937f2d727be 100644 --- a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/DefaultLESSSkinFileCompiler.java +++ b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/DefaultLESSSkinFileCompiler.java @@ -42,6 +42,7 @@ import org.xwiki.model.reference.DocumentReference; import org.xwiki.model.reference.DocumentReferenceResolver; import org.xwiki.model.reference.WikiReference; +import org.xwiki.wiki.descriptor.WikiDescriptorManager; import com.xpn.xwiki.XWiki; import com.xpn.xwiki.XWikiContext; @@ -70,6 +71,9 @@ public class DefaultLESSSkinFileCompiler extends AbstractCachedCompiler<String> @Inject private DocumentReferenceResolver<String> documentReferenceResolver; + @Inject + private WikiDescriptorManager wikiDescriptorManager; + @Override public void initialize() throws InitializationException { diff --git a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/LESSExportActionListener.java b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/LESSExportActionListener.java index b12cf3ecdda..56fa74ee68d 100644 --- a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/LESSExportActionListener.java +++ b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/java/org/xwiki/lesscss/internal/LESSExportActionListener.java @@ -27,6 +27,7 @@ import org.xwiki.bridge.event.ActionExecutingEvent; import org.xwiki.component.annotation.Component; +import org.xwiki.lesscss.ColorThemeCache; import org.xwiki.lesscss.LESSSkinFileCache; import org.xwiki.observation.EventListener; import org.xwiki.observation.event.Event; @@ -48,6 +49,12 @@ public class LESSExportActionListener implements EventListener @Inject private LESSSkinFileCache lessSkinFileCache; + @Inject + private ColorThemeCache colorThemeCache; + + @Inject + private CurrentColorThemeGetter currentColorThemeGetter; + @Override public String getName() { @@ -69,7 +76,9 @@ public void onEvent(Event event, Object source, Object data) XWikiRequest request = xcontext.getRequest(); String format = request.get("format"); if ("html".equals(format)) { - this.lessSkinFileCache.clear(xcontext.getWikiId()); + String colorTheme = currentColorThemeGetter.getCurrentColorTheme("default"); + this.lessSkinFileCache.clearFromColorTheme(colorTheme); + this.colorThemeCache.clearFromColorTheme(colorTheme); } } } diff --git a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/resources/META-INF/components.txt b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/resources/META-INF/components.txt index 1cea549b558..5a6d1a3d4de 100644 --- a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/resources/META-INF/components.txt +++ b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/main/resources/META-INF/components.txt @@ -1,7 +1,8 @@ +org.xwiki.lesscss.internal.ColorThemeListener org.xwiki.lesscss.internal.DefaultColorThemeCache +org.xwiki.lesscss.internal.DefaultCurrentColorThemeGetter org.xwiki.lesscss.internal.DefaultLESSColorThemeConverter org.xwiki.lesscss.internal.DefaultLESSCompiler org.xwiki.lesscss.internal.DefaultLESSSkinFileCache org.xwiki.lesscss.internal.DefaultLESSSkinFileCompiler -org.xwiki.lesscss.internal.SkinAndColorThemeListener -org.xwiki.lesscss.internal.LESSExportActionListener \ No newline at end of file +org.xwiki.lesscss.internal.LESSExportActionListener diff --git a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/test/java/org/xwiki/lesscss/internal/SkinAndColorThemeListenerTest.java b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/test/java/org/xwiki/lesscss/internal/ColorThemeListenerTest.java similarity index 77% rename from xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/test/java/org/xwiki/lesscss/internal/SkinAndColorThemeListenerTest.java rename to xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/test/java/org/xwiki/lesscss/internal/ColorThemeListenerTest.java index 51a19c46309..cfbd93f0683 100644 --- a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/test/java/org/xwiki/lesscss/internal/SkinAndColorThemeListenerTest.java +++ b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/test/java/org/xwiki/lesscss/internal/ColorThemeListenerTest.java @@ -29,10 +29,13 @@ import org.xwiki.bridge.event.DocumentCreatedEvent; import org.xwiki.bridge.event.DocumentDeletedEvent; import org.xwiki.bridge.event.DocumentUpdatedEvent; +import org.xwiki.component.util.DefaultParameterizedType; +import org.xwiki.lesscss.ColorThemeCache; import org.xwiki.lesscss.LESSSkinFileCache; import org.xwiki.model.EntityType; import org.xwiki.model.reference.DocumentReference; import org.xwiki.model.reference.EntityReference; +import org.xwiki.model.reference.EntityReferenceSerializer; import org.xwiki.model.reference.LocalDocumentReference; import org.xwiki.observation.event.Event; import org.xwiki.test.mockito.MockitoComponentMockingRule; @@ -42,27 +45,35 @@ import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; /** - * Test class for {@link SkinAndColorThemeListener}. + * Test class for {@link ColorThemeListener}. * - * @since 6.1M2 + * @since 6.3M2 * @version $Id$ */ -public class SkinAndColorThemeListenerTest +public class ColorThemeListenerTest { @Rule - public MockitoComponentMockingRule<SkinAndColorThemeListener> mocker = - new MockitoComponentMockingRule<>(SkinAndColorThemeListener.class); + public MockitoComponentMockingRule<ColorThemeListener> mocker = + new MockitoComponentMockingRule<>(ColorThemeListener.class); private LESSSkinFileCache lessSkinFileCache; + + private ColorThemeCache colorThemeCache; + + private EntityReferenceSerializer<String> entityReferenceSerializer; + @Before public void setUp() throws Exception { lessSkinFileCache = mocker.getInstance(LESSSkinFileCache.class); + colorThemeCache = mocker.getInstance(ColorThemeCache.class); + entityReferenceSerializer = mocker.getInstance( + new DefaultParameterizedType(null, EntityReferenceSerializer.class, String.class)); } @Test @@ -98,12 +109,14 @@ public void onEventWhenFlamingoThemeChanged() throws Exception DocumentReference documentReference = new DocumentReference("wiki", "space", "page"); when(doc.getDocumentReference()).thenReturn(documentReference); + when(entityReferenceSerializer.serialize(documentReference)).thenReturn("wiki:space.page"); // Test mocker.getComponentUnderTest().onEvent(event, doc, data); // Verify - verify(lessSkinFileCache).clear("wiki"); + verify(lessSkinFileCache).clearFromColorTheme("wiki:space.page"); + verify(colorThemeCache).clearFromColorTheme("wiki:space.page"); } @Test @@ -122,36 +135,14 @@ public void onEventWhenColorThemeChanged() throws Exception DocumentReference documentReference = new DocumentReference("wiki", "space", "page"); when(doc.getDocumentReference()).thenReturn(documentReference); + when(entityReferenceSerializer.serialize(documentReference)).thenReturn("wiki:space.page"); // Test mocker.getComponentUnderTest().onEvent(event, doc, data); // Verify - verify(lessSkinFileCache).clear("wiki"); - } - - @Test - public void onEventWhenSkinChanged() throws Exception - { - // Mocks - Event event = mock(Event.class); - XWikiDocument doc = mock(XWikiDocument.class); - Object data = new Object(); - - EntityReference classReference = new LocalDocumentReference("XWiki", "XWikiSkins"); - List<BaseObject> objects = new ArrayList<>(); - BaseObject object = mock(BaseObject.class); - objects.add(object); - when(doc.getXObjects(classReference)).thenReturn(objects); - - DocumentReference documentReference = new DocumentReference("wiki", "space", "page"); - when(doc.getDocumentReference()).thenReturn(documentReference); - - // Test - mocker.getComponentUnderTest().onEvent(event, doc, data); - - // Verify - verify(lessSkinFileCache).clear("wiki"); + verify(lessSkinFileCache).clearFromColorTheme("wiki:space.page"); + verify(colorThemeCache).clearFromColorTheme("wiki:space.page"); } @Test @@ -171,6 +162,7 @@ public void onEventWhenNoObject() throws Exception mocker.getComponentUnderTest().onEvent(event, doc, data); // Verify - verify(lessSkinFileCache, never()).clear("wikiId"); + verifyZeroInteractions(lessSkinFileCache); + verifyZeroInteractions(colorThemeCache); } } diff --git a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/test/java/org/xwiki/lesscss/internal/DefaultCurrentColorThemeGetterTest.java b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/test/java/org/xwiki/lesscss/internal/DefaultCurrentColorThemeGetterTest.java new file mode 100644 index 00000000000..1bbdd33c44d --- /dev/null +++ b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/test/java/org/xwiki/lesscss/internal/DefaultCurrentColorThemeGetterTest.java @@ -0,0 +1,107 @@ +/* + * 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 org.xwiki.lesscss.internal; + +import javax.inject.Provider; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.xwiki.component.util.DefaultParameterizedType; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.model.reference.DocumentReferenceResolver; +import org.xwiki.model.reference.EntityReferenceSerializer; +import org.xwiki.model.reference.WikiReference; +import org.xwiki.test.mockito.MockitoComponentMockingRule; +import org.xwiki.wiki.descriptor.WikiDescriptorManager; + +import com.xpn.xwiki.XWiki; +import com.xpn.xwiki.XWikiContext; +import com.xpn.xwiki.web.XWikiRequest; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Test class for {@link org.xwiki.lesscss.internal.CurrentColorThemeGetter}. + * + * @since 6.3M2 + * @version $Id$ + */ +public class DefaultCurrentColorThemeGetterTest +{ + @Rule + public MockitoComponentMockingRule<DefaultCurrentColorThemeGetter> mocker = + new MockitoComponentMockingRule<>(DefaultCurrentColorThemeGetter.class); + + private DocumentReferenceResolver<String> documentReferenceResolver; + + private EntityReferenceSerializer<String> entityReferenceSerializer; + + private Provider<XWikiContext> xcontextProvider; + + private WikiDescriptorManager wikiDescriptorManager; + + private XWikiContext xcontext; + + private XWiki xwiki; + + @Before + public void setUp() throws Exception + { + wikiDescriptorManager = mocker.getInstance(WikiDescriptorManager.class); + documentReferenceResolver = mocker.getInstance(new DefaultParameterizedType(null, DocumentReferenceResolver.class, + String.class)); + entityReferenceSerializer = mocker.getInstance(new DefaultParameterizedType(null, EntityReferenceSerializer.class, + String.class)); + xcontextProvider = mocker.getInstance(new DefaultParameterizedType(null, Provider.class, XWikiContext.class)); + xcontext = mock(XWikiContext.class); + when(xcontextProvider.get()).thenReturn(xcontext); + xwiki = mock(XWiki.class); + when(xcontext.getWiki()).thenReturn(xwiki); + + when(wikiDescriptorManager.getCurrentWikiId()).thenReturn("wikiId"); + XWikiRequest request = mock(XWikiRequest.class); + when(xcontext.getRequest()).thenReturn(request); + when(request.getParameter("colorTheme")).thenReturn("myColorTheme"); + DocumentReference colorThemeReference = new DocumentReference("wikiId", "XWiki", "MyColorTheme"); + WikiReference mainWikiReference = new WikiReference("wikiId"); + when(documentReferenceResolver.resolve(eq("myColorTheme"), eq(mainWikiReference))).thenReturn(colorThemeReference); + when(entityReferenceSerializer.serialize(colorThemeReference)).thenReturn("wikiId:ColorTheme.MyColorTheme"); + when(xwiki.exists(colorThemeReference, xcontext)).thenReturn(true); + } + + @Test + public void getCurrentColorThemeTest() throws Exception + { + assertEquals("wikiId:ColorTheme.MyColorTheme", mocker.getComponentUnderTest().getCurrentColorTheme("default")); + } + + @Test + public void getCurrentColorThemeFallbackTest() throws Exception + { + when(xwiki.exists(any(DocumentReference.class), eq(xcontext))).thenReturn(false); + assertEquals("fallback", mocker.getComponentUnderTest().getCurrentColorTheme("fallback")); + assertEquals("error", mocker.getComponentUnderTest().getCurrentColorTheme("error")); + } +} diff --git a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/test/java/org/xwiki/lesscss/internal/DefaultLESSSkinFileCacheTest.java b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/test/java/org/xwiki/lesscss/internal/DefaultLESSSkinFileCacheTest.java index 0e8992de639..ab7744ca6cc 100644 --- a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/test/java/org/xwiki/lesscss/internal/DefaultLESSSkinFileCacheTest.java +++ b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/test/java/org/xwiki/lesscss/internal/DefaultLESSSkinFileCacheTest.java @@ -26,9 +26,16 @@ import org.xwiki.cache.CacheFactory; import org.xwiki.cache.CacheManager; import org.xwiki.cache.config.CacheConfiguration; +import org.xwiki.component.util.DefaultParameterizedType; +import org.xwiki.model.reference.DocumentReference; +import org.xwiki.model.reference.DocumentReferenceResolver; +import org.xwiki.model.reference.EntityReferenceSerializer; +import org.xwiki.model.reference.WikiReference; import org.xwiki.test.mockito.MockitoComponentMockingRule; +import org.xwiki.wiki.descriptor.WikiDescriptorManager; import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -52,6 +59,12 @@ public class DefaultLESSSkinFileCacheTest private Cache<String> cache; + private DocumentReferenceResolver<String> documentReferenceResolver; + + private EntityReferenceSerializer<String> entityReferenceSerializer; + + private WikiDescriptorManager wikiDescriptorManager; + @Before public void setUp() throws Exception { @@ -61,16 +74,25 @@ public void setUp() throws Exception when(cacheManager.getCacheFactory()).thenReturn(cacheFactory); CacheConfiguration configuration = new CacheConfiguration("lesscss.skinfiles.cache"); when(cacheFactory.<String>newCache(eq(configuration))).thenReturn(cache); + documentReferenceResolver = mocker.getInstance( + new DefaultParameterizedType(null, DocumentReferenceResolver.class, String.class)); + entityReferenceSerializer = mocker.getInstance( + new DefaultParameterizedType(null, EntityReferenceSerializer.class, String.class)); + wikiDescriptorManager = mocker.getInstance(WikiDescriptorManager.class); + DocumentReference colorThemeRef = new DocumentReference("currentWiki", "ColorTheme", "CT"); + when(documentReferenceResolver.resolve(eq("colorTheme"), any(WikiReference.class))).thenReturn(colorThemeRef); + when(entityReferenceSerializer.serialize(colorThemeRef)).thenReturn("currentWiki:ColorTheme.CT"); + when(wikiDescriptorManager.getCurrentWikiId()).thenReturn("currentWiki"); } @Test public void get() throws Exception { // Mock - when(cache.get("6wikiId_4skin_10colorTheme_4file")).thenReturn("Expected output"); + when(cache.get("4skin_25currentWiki:ColorTheme.CT_4file")).thenReturn("Expected output"); // Test - String result = mocker.getComponentUnderTest().get("file", "wikiId", "skin", "colorTheme"); + String result = mocker.getComponentUnderTest().get("file", "skin", "colorTheme"); // Verify assertEquals("Expected output", result); @@ -80,10 +102,10 @@ public void get() throws Exception public void set() throws Exception { // Test - mocker.getComponentUnderTest().set("file", "wikiId", "skin", "colorTheme", "css"); + mocker.getComponentUnderTest().set("file", "skin", "colorTheme", "css"); // Verify - verify(cache).set(eq("6wikiId_4skin_10colorTheme_4file"), eq("css")); + verify(cache).set(eq("4skin_25currentWiki:ColorTheme.CT_4file"), eq("css")); } @Test @@ -97,25 +119,50 @@ public void clear() throws Exception } @Test - public void clearWithParams() throws Exception + public void clearFromFileSystemSkin() throws Exception + { + // Init + + // Add the first one twice + mocker.getComponentUnderTest().set("file1", "skin1", "colorTheme", "css1"); + mocker.getComponentUnderTest().set("file1", "skin1", "colorTheme", "css1"); + + // Others + mocker.getComponentUnderTest().set("file1", "skin2", "colorTheme", "css2"); + mocker.getComponentUnderTest().set("file2", "skin1", "colorTheme", "css3"); + + // Testskin1 + mocker.getComponentUnderTest().clearFromFileSystemSkin("skin1"); + + // Verify + verify(cache, times(1)).remove("5skin1_25currentWiki:ColorTheme.CT_5file1"); + verify(cache).remove("5skin1_25currentWiki:ColorTheme.CT_5file2"); + verify(cache, never()).remove("5skin2_25currentWiki:ColorTheme.CT_5file1"); + } + + @Test + public void clearFromColorTheme() throws Exception { // Init + DocumentReference colorThemeRef2 = new DocumentReference("currentWiki", "ColorTheme", "CT2"); + when(documentReferenceResolver.resolve(eq("colorTheme2"), any(WikiReference.class))).thenReturn(colorThemeRef2); + when(entityReferenceSerializer.serialize(colorThemeRef2)).thenReturn("currentWiki:ColorTheme.CT2"); // Add the first one twice - mocker.getComponentUnderTest().set("file1", "wiki1", "skin1", "colorTheme1", "css1"); - mocker.getComponentUnderTest().set("file1", "wiki1", "skin1", "colorTheme1", "css1"); + mocker.getComponentUnderTest().set("file1", "skin1", "colorTheme", "css1"); + mocker.getComponentUnderTest().set("file1", "skin1", "colorTheme", "css1"); // Others - mocker.getComponentUnderTest().set("file1", "wiki1", "skin2", "colorTheme1", "css2"); - mocker.getComponentUnderTest().set("file1", "wiki2", "skin1", "colorTheme1", "css3"); + mocker.getComponentUnderTest().set("file1", "skin1", "colorTheme2", "css2"); + mocker.getComponentUnderTest().set("file1", "skin2", "colorTheme", "css3"); // Test - mocker.getComponentUnderTest().clear("wiki1"); + mocker.getComponentUnderTest().clearFromColorTheme("colorTheme"); // Verify - verify(cache, times(1)).remove("5wiki1_5skin1_11colorTheme1_5file1"); - verify(cache).remove("5wiki1_5skin2_11colorTheme1_5file1"); - verify(cache, never()).remove("5wiki2_5skin1_11colorTheme1_5file1"); + verify(cache, times(1)).remove("5skin1_25currentWiki:ColorTheme.CT_5file1"); + verify(cache).remove("5skin2_25currentWiki:ColorTheme.CT_5file1"); + verify(cache, never()).remove("5skin2_26currentWiki:ColorTheme.CT2_5file1"); } } diff --git a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/test/java/org/xwiki/lesscss/internal/DefaultLESSSkinFileCompilerTest.java b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/test/java/org/xwiki/lesscss/internal/DefaultLESSSkinFileCompilerTest.java index 6eafd5acbc1..9a2f7095144 100644 --- a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/test/java/org/xwiki/lesscss/internal/DefaultLESSSkinFileCompilerTest.java +++ b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/test/java/org/xwiki/lesscss/internal/DefaultLESSSkinFileCompilerTest.java @@ -34,7 +34,6 @@ import org.xwiki.lesscss.LESSSkinFileCache; import org.xwiki.model.reference.DocumentReference; import org.xwiki.model.reference.DocumentReferenceResolver; -import org.xwiki.model.reference.EntityReferenceSerializer; import org.xwiki.model.reference.WikiReference; import org.xwiki.test.mockito.MockitoComponentMockingRule; import org.xwiki.wiki.descriptor.WikiDescriptorManager; @@ -45,7 +44,6 @@ import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.objects.BaseObject; import com.xpn.xwiki.web.XWikiEngineContext; -import com.xpn.xwiki.web.XWikiRequest; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -77,26 +75,25 @@ public class DefaultLESSSkinFileCompilerTest private Provider<XWikiContext> xcontextProvider; + private CurrentColorThemeGetter currentColorThemeGetter; + + private DocumentReferenceResolver<String> referenceResolver; + private XWikiContext xcontext; private XWiki xwiki; private XWikiEngineContext engineContext; - private DocumentReferenceResolver<String> referenceResolver; - - private EntityReferenceSerializer<String> referenceSerializer; - @Before public void setUp() throws Exception { lessCompiler = mocker.getInstance(LESSCompiler.class); wikiDescriptorManager = mocker.getInstance(WikiDescriptorManager.class); - referenceResolver = mocker.getInstance(new DefaultParameterizedType(null, DocumentReferenceResolver.class, - String.class)); - referenceSerializer = mocker.getInstance(new DefaultParameterizedType(null, EntityReferenceSerializer.class, - String.class)); cache = mocker.getInstance(LESSSkinFileCache.class); + currentColorThemeGetter = mocker.getInstance(CurrentColorThemeGetter.class); + referenceResolver = mocker.getInstance(new DefaultParameterizedType(null, DocumentReferenceResolver.class, + String.class)); xcontextProvider = mocker.getInstance(new DefaultParameterizedType(null, Provider.class, XWikiContext.class)); xcontext = mock(XWikiContext.class); when(xcontextProvider.get()).thenReturn(xcontext); @@ -104,16 +101,12 @@ public void setUp() throws Exception when(xcontext.getWiki()).thenReturn(xwiki); engineContext = mock(XWikiEngineContext.class); when(xwiki.getEngineContext()).thenReturn(engineContext); - XWikiRequest request = mock(XWikiRequest.class); - when(xcontext.getRequest()).thenReturn(request); - when(request.getParameter("colorTheme")).thenReturn("myColorTheme"); + when(wikiDescriptorManager.getCurrentWikiId()).thenReturn("wikiId"); when(xwiki.getSkin(xcontext)).thenReturn("skin"); - DocumentReference colorThemeReference = new DocumentReference("wikiId", "XWiki", "MyColorTheme"); - WikiReference mainWikiReference = new WikiReference("wikiId"); - when(referenceResolver.resolve(eq("myColorTheme"), eq(mainWikiReference))).thenReturn(colorThemeReference); - when(referenceSerializer.serialize(colorThemeReference)).thenReturn("wikiId:ColorTheme.MyColorTheme"); - when(xwiki.exists(colorThemeReference, xcontext)).thenReturn(true); + + when(currentColorThemeGetter.getCurrentColorTheme("default")).thenReturn("wikiId:ColorTheme.MyColorTheme"); + } private void prepareMocksForCompilation() throws Exception @@ -158,8 +151,8 @@ public void compileSkinFile() throws Exception assertEquals("OUTPUT", mocker.getComponentUnderTest().compileSkinFile("style2.less", false)); // Verify - verify(cache).get(eq("style2.less"), eq("wikiId"), eq("skin"), eq("wikiId:ColorTheme.MyColorTheme")); - verify(cache).set(eq("style2.less"), eq("wikiId"), eq("skin"), eq("wikiId:ColorTheme.MyColorTheme"), + verify(cache).get(eq("style2.less"), eq("skin"), eq("wikiId:ColorTheme.MyColorTheme")); + verify(cache).set(eq("style2.less"), eq("skin"), eq("wikiId:ColorTheme.MyColorTheme"), eq("OUTPUT")); } @@ -167,7 +160,7 @@ public void compileSkinFile() throws Exception public void compileSkinFileWhenInCache() throws Exception { // Mock - when(cache.get("style2.less", "wikiId", "skin", "wikiId:ColorTheme.MyColorTheme")).thenReturn("OUTPUT"); + when(cache.get("style2.less", "skin", "wikiId:ColorTheme.MyColorTheme")).thenReturn("OUTPUT"); // Test assertEquals("OUTPUT", mocker.getComponentUnderTest().compileSkinFile("style2.less", false)); @@ -181,32 +174,14 @@ public void compileSkinFileWhenInCache() throws Exception public void compileSkinFileWhenInCacheButForce() throws Exception { // Mock - when(cache.get("style2.less", "wikiId", "skin", "wikiId:ColorTheme.MyColorTheme")).thenReturn("OLD OUTPUT"); + when(cache.get("style2.less", "skin", "wikiId:ColorTheme.MyColorTheme")).thenReturn("OLD OUTPUT"); prepareMocksForCompilation(); // Test assertEquals("OUTPUT", mocker.getComponentUnderTest().compileSkinFile("style2.less", true)); // Verify - verify(cache).set(eq("style2.less"), eq("wikiId"), eq("skin"), eq("wikiId:ColorTheme.MyColorTheme"), - eq("OUTPUT")); - } - - @Test - public void compileSkinFileWhenColorThemeDoesNotExist() throws Exception - { - // Mock - when(cache.get("style2.less", "wikiId", "skin", "default")).thenReturn("DEFAULT COLOR THEME"); - DocumentReference colorThemeReference = new DocumentReference("wikiId", "XWiki", "invalidColorTheme"); - when(referenceResolver.resolve("invalidColorTheme")).thenReturn(colorThemeReference); - when(xwiki.exists(colorThemeReference, xcontext)).thenReturn(false); - prepareMocksForCompilation(); - XWikiRequest request = mock(XWikiRequest.class); - when(xcontext.getRequest()).thenReturn(request); - when(request.getParameter("colorTheme")).thenReturn("invalidColorTheme"); - - // Test - assertEquals("DEFAULT COLOR THEME", mocker.getComponentUnderTest().compileSkinFile("style2.less", false)); + verify(cache).set(eq("style2.less"), eq("skin"), eq("wikiId:ColorTheme.MyColorTheme"), eq("OUTPUT")); } @Test @@ -254,10 +229,9 @@ public void compileSkinFileWhenSkinIsOnDB() throws Exception assertEquals("OUTPUT", mocker.getComponentUnderTest().compileSkinFile("style2.less", false)); // Verify - verify(cache).get(eq("style2.less"), eq("wikiId"), eq("XWiki.DefaultSkin"), - eq("wikiId:ColorTheme.MyColorTheme")); - verify(cache).set(eq("style2.less"), eq("wikiId"), eq("XWiki.DefaultSkin"), - eq("wikiId:ColorTheme.MyColorTheme"), eq("OUTPUT")); + verify(cache).get(eq("style2.less"), eq("XWiki.DefaultSkin"), eq("wikiId:ColorTheme.MyColorTheme")); + verify(cache).set(eq("style2.less"), eq("XWiki.DefaultSkin"), eq("wikiId:ColorTheme.MyColorTheme"), + eq("OUTPUT")); verify(xcontext, never()).put(anyString(), anyString()); } @@ -377,9 +351,8 @@ public void compileSkinFileWhenSkinDocumentHasNoObject() throws Exception assertEquals("OUTPUT", mocker.getComponentUnderTest().compileSkinFile("style2.less", false)); // Verify - verify(cache).get(eq("style2.less"), eq("wikiId"), eq("flamingo"), eq("wikiId:ColorTheme.MyColorTheme")); - verify(cache).set(eq("style2.less"), eq("wikiId"), eq("flamingo"), eq("wikiId:ColorTheme.MyColorTheme"), - eq("OUTPUT")); + verify(cache).get(eq("style2.less"), eq("flamingo"), eq("wikiId:ColorTheme.MyColorTheme")); + verify(cache).set(eq("style2.less"), eq("flamingo"), eq("wikiId:ColorTheme.MyColorTheme"), eq("OUTPUT")); verify(xcontext, never()).put(anyString(), anyString()); } @@ -390,12 +363,8 @@ public void compileSkinFileOnSubwiki() throws Exception prepareMocksForCompilation(); when(xwiki.getSkin(xcontext)).thenReturn("XWiki.DefaultSkin"); WikiReference currentWikiReference = new WikiReference("wikiId"); - DocumentReference colorThemeRef = new DocumentReference("wikiId", "ColorTheme", "MyColorTheme"); - when(referenceResolver.resolve(eq("myColorTheme"), eq(currentWikiReference))).thenReturn(colorThemeRef); - when(referenceSerializer.serialize(colorThemeRef)).thenReturn("wikiId:ColorTheme.MyColorTheme"); - when(xwiki.exists(colorThemeRef, xcontext)).thenReturn(true); - when(cache.get(eq("style.less"), eq("wikiId"), eq("XWiki.DefaultSkin"), eq("wikiId:ColorTheme.MyColorTheme"))). + when(cache.get(eq("style.less"), eq("XWiki.DefaultSkin"), eq("wikiId:ColorTheme.MyColorTheme"))). thenReturn("SUBWIKI OUTPUT"); // Test @@ -407,11 +376,6 @@ public void compileSkinFileOnOtherSkin() throws Exception { // Mocks prepareMocksForCompilationOnFlamingo(); - WikiReference currentWikiReference = new WikiReference("wikiId"); - DocumentReference colorThemeRef = new DocumentReference("wikiId", "ColorTheme", "MyColorTheme"); - when(referenceResolver.resolve(eq("myColorTheme"), eq(currentWikiReference))).thenReturn(colorThemeRef); - when(referenceSerializer.serialize(colorThemeRef)).thenReturn("wikiId:ColorTheme.MyColorTheme"); - when(xwiki.exists(colorThemeRef, xcontext)).thenReturn(true); // Test assertEquals("OUTPUT", mocker.getComponentUnderTest().compileSkinFile("style2.less", "flamingo", false)); @@ -426,11 +390,6 @@ public void compileSkinFileOnOtherSkinWithException() throws Exception { // Mocks prepareMocksForCompilationOnFlamingo(); - WikiReference currentWikiReference = new WikiReference("wikiId"); - DocumentReference colorThemeRef = new DocumentReference("wikiId", "ColorTheme", "MyColorTheme"); - when(referenceResolver.resolve(eq("myColorTheme"), eq(currentWikiReference))).thenReturn(colorThemeRef); - when(referenceSerializer.serialize(colorThemeRef)).thenReturn("wikiId:ColorTheme.MyColorTheme"); - when(xwiki.exists(colorThemeRef, xcontext)).thenReturn(true); Exception exception = new LESSCompilerException("Exception with LESS", null); when(lessCompiler.compile(anyString(), any(Path[].class))).thenThrow(exception); @@ -454,12 +413,6 @@ public void compileSkinFileWhenDirectoryDoesNotExist() throws Exception { when(engineContext.getRealPath("/skins/flamingo/less")).thenReturn("ighgzuheubigvugvbzekvbzekvuzkkkhguiiiii"); - WikiReference currentWikiReference = new WikiReference("wikiId"); - DocumentReference colorThemeRef = new DocumentReference("wikiId", "ColorTheme", "MyColorTheme"); - when(referenceResolver.resolve(eq("myColorTheme"), eq(currentWikiReference))).thenReturn(colorThemeRef); - when(referenceSerializer.serialize(colorThemeRef)).thenReturn("wikiId:ColorTheme.MyColorTheme"); - when(xwiki.exists(colorThemeRef, xcontext)).thenReturn(true); - // Test Exception exceptionCaught = null; try { @@ -503,11 +456,6 @@ public void compileSkinFileOnSubwikiWhenSkinIsOnMainWiki() throws Exception // Mocks prepareMocksForCompilation(); when(xwiki.getSkin(xcontext)).thenReturn("mainWiki:XWiki.DefaultSkin"); - WikiReference currentWikiReference = new WikiReference("wikiId"); - DocumentReference colorThemeRef = new DocumentReference("wikiId", "ColorTheme", "MyColorTheme"); - when(referenceResolver.resolve(eq("myColorTheme"), eq(currentWikiReference))).thenReturn(colorThemeRef); - when(referenceSerializer.serialize(colorThemeRef)).thenReturn("wikiId:ColorTheme.MyColorTheme"); - when(xwiki.exists(colorThemeRef, xcontext)).thenReturn(true); DocumentReference skinDocRef = new DocumentReference("mainWiki", "XWiki", "DefaultSkin"); when(referenceResolver.resolve(eq("mainWiki:XWiki.DefaultSkin"), any(WikiReference.class))) diff --git a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/test/java/org/xwiki/lesscss/internal/LESSExportActionListenerTest.java b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/test/java/org/xwiki/lesscss/internal/LESSExportActionListenerTest.java index 0fec3348e01..617fe725ed3 100644 --- a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/test/java/org/xwiki/lesscss/internal/LESSExportActionListenerTest.java +++ b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-default/src/test/java/org/xwiki/lesscss/internal/LESSExportActionListenerTest.java @@ -22,6 +22,7 @@ import org.junit.Rule; import org.junit.Test; import org.xwiki.bridge.event.ActionExecutingEvent; +import org.xwiki.lesscss.ColorThemeCache; import org.xwiki.lesscss.LESSSkinFileCache; import org.xwiki.test.mockito.MockitoComponentMockingRule; @@ -64,12 +65,16 @@ public void onEventWhenHTMLExport() throws Exception when(xcontext.getRequest()).thenReturn(request); when(request.get("format")).thenReturn("html"); when(xcontext.getWikiId()).thenReturn("wiki"); + CurrentColorThemeGetter currentColorThemeGetter = this.mocker.getInstance(CurrentColorThemeGetter.class); + when(currentColorThemeGetter.getCurrentColorTheme("default")).thenReturn("colorTheme"); this.mocker.getComponentUnderTest().onEvent(new ActionExecutingEvent("export"), null, xcontext); // The test is here: we verify that the clear API was called! LESSSkinFileCache cache = this.mocker.getInstance(LESSSkinFileCache.class); - verify(cache).clear("wiki"); + ColorThemeCache cache2 = this.mocker.getInstance(ColorThemeCache.class); + verify(cache).clearFromColorTheme("colorTheme"); + verify(cache2).clearFromColorTheme("colorTheme"); } @Test @@ -86,5 +91,7 @@ public void onEventWhenNonHTMLExport() throws Exception // Actually that the cache object was not called at all... LESSSkinFileCache cache = this.mocker.getInstance(LESSSkinFileCache.class); verifyZeroInteractions(cache); + CurrentColorThemeGetter currentColorThemeGetter = this.mocker.getInstance(CurrentColorThemeGetter.class); + verifyZeroInteractions(currentColorThemeGetter); } } diff --git a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-script/src/main/java/org/xwiki/lesscss/LessCompilerScriptService.java b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-script/src/main/java/org/xwiki/lesscss/LessCompilerScriptService.java index 24b42295313..bef2b346f41 100644 --- a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-script/src/main/java/org/xwiki/lesscss/LessCompilerScriptService.java +++ b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-script/src/main/java/org/xwiki/lesscss/LessCompilerScriptService.java @@ -46,7 +46,10 @@ public class LessCompilerScriptService implements ScriptService private LESSSkinFileCompiler lessCompiler; @Inject - private LESSSkinFileCache cache; + private LESSSkinFileCache lessCache; + + @Inject + private ColorThemeCache colorThemeCache; @Inject private Provider<XWikiContext> xcontextProvider; @@ -166,17 +169,39 @@ public boolean clearCache() return false; } - cache.clear(); + lessCache.clear(); + colorThemeCache.clear(); + return true; + } + + /** + * Remove every generated files corresponding to a color theme. + * The script calling this method needs the programming rights. + * @param colorTheme fullname of the color theme + * @return true if the operation succeed + */ + public boolean clearCacheFromColorTheme(String colorTheme) + { + XWikiContext xcontext = xcontextProvider.get(); + + // Check if the current script has the programing rights + if (!authorizationManager.hasAccess(Right.PROGRAM, xcontext.getDoc().getAuthorReference(), + xcontext.getDoc().getDocumentReference())) { + return false; + } + + lessCache.clearFromColorTheme(colorTheme); + colorThemeCache.clearFromColorTheme(colorTheme); return true; } /** - * Remove every generated files corresponding to a wiki. + * Remove every generated files corresponding to a filesystem skin. * The script calling this method needs the programming rights. - * @param wikiId id of the wiki + * @param skin name of the filesystem skin * @return true if the operation succeed */ - public boolean clearCache(String wikiId) + public boolean clearCacheFromFileSystemSkin(String skin) { XWikiContext xcontext = xcontextProvider.get(); @@ -186,7 +211,8 @@ public boolean clearCache(String wikiId) return false; } - cache.clear(wikiId); + lessCache.clearFromFileSystemSkin(skin); + colorThemeCache.clearFromFileSystemSkin(skin); return true; } } diff --git a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-script/src/test/java/org/xwiki/lesscss/LessCompilerScriptServiceTest.java b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-script/src/test/java/org/xwiki/lesscss/LessCompilerScriptServiceTest.java index ebd72a1e994..a46437d8c5e 100644 --- a/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-script/src/test/java/org/xwiki/lesscss/LessCompilerScriptServiceTest.java +++ b/xwiki-platform-core/xwiki-platform-lesscss/xwiki-platform-lesscss-script/src/test/java/org/xwiki/lesscss/LessCompilerScriptServiceTest.java @@ -38,8 +38,8 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; /** @@ -56,7 +56,9 @@ public class LessCompilerScriptServiceTest private LESSSkinFileCompiler lessCompiler; - private LESSSkinFileCache cache; + private LESSSkinFileCache lessCache; + + private ColorThemeCache colorThemeCache; private LESSColorThemeConverter lessColorThemeConverter; @@ -71,7 +73,8 @@ public void setUp() throws Exception { lessCompiler = mocker.getInstance(LESSSkinFileCompiler.class); lessColorThemeConverter = mocker.getInstance(LESSColorThemeConverter.class); - cache = mocker.getInstance(LESSSkinFileCache.class); + lessCache = mocker.getInstance(LESSSkinFileCache.class); + colorThemeCache = mocker.getInstance(ColorThemeCache.class); authorizationManager = mocker.getInstance(AuthorizationManager.class); xcontextProvider = mocker.getInstance(new DefaultParameterizedType(null, Provider.class, XWikiContext.class)); xcontext = mock(XWikiContext.class); @@ -212,7 +215,8 @@ public void clearCacheWithRights() throws Exception assertTrue(mocker.getComponentUnderTest().clearCache()); // Verify - verify(cache).clear(); + verify(lessCache).clear(); + verify(colorThemeCache).clear(); } @Test @@ -232,11 +236,54 @@ public void clearCacheWithoutRights() throws Exception assertFalse(mocker.getComponentUnderTest().clearCache()); // Verify - verify(cache, never()).clear(); + verifyZeroInteractions(lessCache); + verifyZeroInteractions(colorThemeCache); + } + + @Test + public void clearCacheFromColorThemeWithRights() throws Exception + { + // Mocks + XWikiDocument doc = mock(XWikiDocument.class); + DocumentReference authorReference = new DocumentReference("wiki", "Space", "User"); + when(xcontext.getDoc()).thenReturn(doc); + when(doc.getAuthorReference()).thenReturn(authorReference); + DocumentReference currentDocReference = new DocumentReference("wiki", "Space", "Page"); + when(doc.getDocumentReference()).thenReturn(currentDocReference); + + when(authorizationManager.hasAccess(Right.PROGRAM, authorReference, currentDocReference)).thenReturn(true); + + // Tests + assertTrue(mocker.getComponentUnderTest().clearCacheFromColorTheme("colorTheme")); + + // Verify + verify(lessCache).clearFromColorTheme(eq("colorTheme")); + verify(colorThemeCache).clearFromColorTheme(eq("colorTheme")); + } + + @Test + public void clearCacheFromColorThemeWithoutRights() throws Exception + { + // Mocks + XWikiDocument doc = mock(XWikiDocument.class); + DocumentReference authorReference = new DocumentReference("wiki", "Space", "User"); + when(xcontext.getDoc()).thenReturn(doc); + when(doc.getAuthorReference()).thenReturn(authorReference); + DocumentReference currentDocReference = new DocumentReference("wiki", "Space", "Page"); + when(doc.getDocumentReference()).thenReturn(currentDocReference); + + when(authorizationManager.hasAccess(Right.PROGRAM, authorReference, currentDocReference)).thenReturn(false); + + // Tests + assertFalse(mocker.getComponentUnderTest().clearCacheFromColorTheme("colorTheme")); + + // Verify + verifyZeroInteractions(lessCache); + verifyZeroInteractions(colorThemeCache); } @Test - public void clearCacheWithParamsWithRights() throws Exception + public void clearCacheFromSkinWithRights() throws Exception { // Mocks XWikiDocument doc = mock(XWikiDocument.class); @@ -249,14 +296,15 @@ public void clearCacheWithParamsWithRights() throws Exception when(authorizationManager.hasAccess(Right.PROGRAM, authorReference, currentDocReference)).thenReturn(true); // Tests - assertTrue(mocker.getComponentUnderTest().clearCache("wiki")); + assertTrue(mocker.getComponentUnderTest().clearCacheFromFileSystemSkin("skin")); // Verify - verify(cache).clear(eq("wiki")); + verify(lessCache).clearFromFileSystemSkin(eq("skin")); + verify(colorThemeCache).clearFromFileSystemSkin(eq("skin")); } @Test - public void clearCacheWithParamsWithoutRights() throws Exception + public void clearCacheFromSkinWithoutRights() throws Exception { // Mocks XWikiDocument doc = mock(XWikiDocument.class); @@ -269,10 +317,11 @@ public void clearCacheWithParamsWithoutRights() throws Exception when(authorizationManager.hasAccess(Right.PROGRAM, authorReference, currentDocReference)).thenReturn(false); // Tests - assertFalse(mocker.getComponentUnderTest().clearCache("wiki")); + assertFalse(mocker.getComponentUnderTest().clearCacheFromFileSystemSkin("skin")); // Verify - verify(cache, never()).clear(eq("wiki")); + verifyZeroInteractions(lessCache); + verifyZeroInteractions(colorThemeCache); } } -- GitLab