From ce1e06ca2cdb98808f4ea6edc60638d2de6a8ab5 Mon Sep 17 00:00:00 2001 From: jensp Date: Thu, 14 Sep 2017 12:29:07 +0000 Subject: [PATCH] Limit LESS CSS compilation to certain files. git-svn-id: https://svn.libreccm.org/ccm/trunk@4999 8810af33-2d31-482b-a856-94f89814c4df --- .../themedirector/ThemeDirectorConfig.java | 290 ++++++++++-------- .../ThemeDirectorConfig_parameter.properties | 4 + .../themedirector/util/ThemeFileUtil.java | 79 ++--- 3 files changed, 192 insertions(+), 181 deletions(-) diff --git a/ccm-themedirector/src/com/arsdigita/themedirector/ThemeDirectorConfig.java b/ccm-themedirector/src/com/arsdigita/themedirector/ThemeDirectorConfig.java index d1cb14268..2ac9e627a 100755 --- a/ccm-themedirector/src/com/arsdigita/themedirector/ThemeDirectorConfig.java +++ b/ccm-themedirector/src/com/arsdigita/themedirector/ThemeDirectorConfig.java @@ -15,7 +15,6 @@ * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ - package com.arsdigita.themedirector; import com.arsdigita.persistence.DataCollection; @@ -30,24 +29,31 @@ import com.arsdigita.util.parameter.IntegerParameter; import java.util.Collection; import java.util.ArrayList; import java.util.Arrays; + import com.arsdigita.util.StringUtils; +import com.arsdigita.util.parameter.StringArrayParameter; import javax.servlet.http.HttpServletRequest; import org.apache.log4j.Logger; /** - * This is the configuration file for the Theme application + * This is the configuration file for the Theme application */ public class ThemeDirectorConfig extends AbstractConfig { - - /** Internal logger instance to faciliate debugging. Enable logging output - * by editing /WEB-INF/conf/log4j.properties int the runtime environment - * and set com.arsdigita.themedirector.ThemeDirectorConfig=DEBUG - * by uncommenting or adding the line. */ - private static final Logger s_log = Logger.getLogger(ThemeDirectorConfig.class); - /** Singelton config object. */ + /** + * Internal logger instance to faciliate debugging. Enable logging output by + * editing /WEB-INF/conf/log4j.properties int the runtime environment and + * set com.arsdigita.themedirector.ThemeDirectorConfig=DEBUG by uncommenting + * or adding the line. + */ + private static final Logger s_log = Logger.getLogger( + ThemeDirectorConfig.class); + + /** + * Singelton config object. + */ private static ThemeDirectorConfig s_conf; /** @@ -55,6 +61,7 @@ public class ThemeDirectorConfig extends AbstractConfig { * * Singelton pattern, don't instantiate a config object using the * constructor directly! + * * @return */ public static synchronized ThemeDirectorConfig getInstance() { @@ -68,71 +75,85 @@ public class ThemeDirectorConfig extends AbstractConfig { // ///////////////////////////////////////////////////////////////// // Set of Configuration Parameters // ///////////////////////////////////////////////////////////////// - - /** Directory that all of the default themes are copied from. */ - private final Parameter m_defaultThemePath = - new StringParameter - ("themedirector.default_theme_path", - Parameter.OPTIONAL, "/themes/master/"); + /** + * Directory that all of the default themes are copied from. + */ + private final Parameter m_defaultThemePath = new StringParameter( + "themedirector.default_theme_path", + Parameter.OPTIONAL, "/themes/master/"); - /** File containing the default themes directory. Used in conjuntion with - com.arsdigita.themedirectory.default_directory_filter to dictate the - final default directory. */ - private final Parameter m_defaultThemeManifest = - new StringParameter - ("themedirector.default_theme_manifest", - Parameter.OPTIONAL, "ccm-themedirector.web.mf"); + /** + * File containing the default themes directory. Used in conjuntion with + * com.arsdigita.themedirectory.default_directory_filter to dictate the + * final default directory. + */ + private final Parameter m_defaultThemeManifest = new StringParameter( + "themedirector.default_theme_manifest", + Parameter.OPTIONAL, "ccm-themedirector.web.mf"); - /** list of file extensions that should be included when the designer - requests 'all graphics files' */ - private final Parameter m_fileExtParam = - new StringParameter - ("themedirector.file_extensions", - Parameter.REQUIRED, "bmp css eot gif jpeg jpg js png svg ttf woff xml xsl"); + /** + * list of file extensions that should be included when the designer + * requests 'all graphics files' + */ + private final Parameter m_fileExtParam = new StringParameter( + "themedirector.file_extensions", + Parameter.REQUIRED, + "bmp css eot gif jpeg jpg js png svg ttf woff xml xsl"); - /** number of seconds before checking for updated development files. - in a multi-jvm installation. (0 means never start) */ - private final Parameter m_themeDevFileWatchStartupDelay = - new IntegerParameter - ("themedirector.theme_dev_file_watch_startup_delay", - Parameter.REQUIRED, new Integer(60*2)); + private final Parameter m_lessFilesParam = new StringArrayParameter( + "themedirectory.less_files.compile", + Parameter.OPTIONAL, + new String[]{"styles/style.less"}); - /** Number of seconds between checks for updated development files - in a multi-jvm installation. */ - private final Parameter m_themeDevFileWatchPollDelay = - new IntegerParameter - ("themedirector.theme_dev_file_watch_poll_delay", - Parameter.REQUIRED, new Integer(60*90)); //once every 90 minutes + /** + * number of seconds before checking for updated development files. in a + * multi-jvm installation. (0 means never start) + */ + private final Parameter m_themeDevFileWatchStartupDelay + = new IntegerParameter( + "themedirector.theme_dev_file_watch_startup_delay", + Parameter.REQUIRED, new Integer(60 * 2)); - /** Number of seconds before checking for update of published theme files - in a multi-jvm installation. (0 means never start) */ - private final Parameter m_themePubFileWatchStartupDelay = - new IntegerParameter - ("themedirector.theme_pub_file_watch_startup_delay", - Parameter.REQUIRED, new Integer(60*2)); + /** + * Number of seconds between checks for updated development files in a + * multi-jvm installation. + */ + private final Parameter m_themeDevFileWatchPollDelay = new IntegerParameter( + "themedirector.theme_dev_file_watch_poll_delay", + Parameter.REQUIRED, new Integer(60 * 90)); //once every 90 minutes - /** number of seconds between checks for recently published theme files - in a multi-jvm installation. */ - private final Parameter m_themePubFileWatchPollDelay = - new IntegerParameter - ("themedirector.theme_pub_file_watch_poll_delay", - Parameter.REQUIRED, new Integer(60*60)); // default to once an hour + /** + * Number of seconds before checking for update of published theme files in + * a multi-jvm installation. (0 means never start) + */ + private final Parameter m_themePubFileWatchStartupDelay + = new IntegerParameter( + "themedirector.theme_pub_file_watch_startup_delay", + Parameter.REQUIRED, new Integer(60 * 2)); - - /** */ + /** + * number of seconds between checks for recently published theme files in a + * multi-jvm installation. + */ + private final Parameter m_themePubFileWatchPollDelay = new IntegerParameter( + "themedirector.theme_pub_file_watch_poll_delay", + Parameter.REQUIRED, new Integer(60 * 60)); // default to once an hour + + /** + * */ private Collection m_downloadFileExtensions = null; - /** - * Constructor. - * Singelton pattern, don't instantiate a config object using the - constructor directly! Use getInstance() instead. + /** + * Constructor. Singelton pattern, don't instantiate a config object using + * the constructor directly! Use getInstance() instead. */ public ThemeDirectorConfig() { register(m_defaultThemePath); register(m_defaultThemeManifest); register(m_fileExtParam); - + register(m_lessFilesParam); + register(m_themeDevFileWatchStartupDelay); register(m_themeDevFileWatchPollDelay); register(m_themePubFileWatchStartupDelay); @@ -141,34 +162,30 @@ public class ThemeDirectorConfig extends AbstractConfig { loadInfo(); } - // ///////////////////////////////////////////////////////////////// // Set of Configuration Parameters // ///////////////////////////////////////////////////////////////// - - /** - * Retrieves the path to a directory containing a complete set of theme - * files to copy into a new theme as a default implementation. - * By default it is set to a master directory containing the distribution's - * default theme. When creating a new theme it is copied over to provide - * a default for the new theme. - * - * Developer's note: - * Previously it was used as a string to filter a files directory stored in - * the Manifest file. Matching files were copied to the new theme's directory. - * The reason to use this approach is not documented. As a guess: it enables - * to copy from more than one directory, if those directories share a common - * name part which is specified here. But because all files must be present - * at deploy time to be included into the Manifest file it makes no sense. - * From original comment: "Specifically, if this is not null - * (or the empty string) than any file that is used as part of - * the default directory must start with this string." - * - * @return name of a directory containing a default theme implementation + * Retrieves the path to a directory containing a complete set of theme + * files to copy into a new theme as a default implementation. By default it + * is set to a master directory containing the distribution's default theme. + * When creating a new theme it is copied over to provide a default for the + * new theme. + * + * Developer's note: Previously it was used as a string to filter a files + * directory stored in the Manifest file. Matching files were copied to the + * new theme's directory. The reason to use this approach is not documented. + * As a guess: it enables to copy from more than one directory, if those + * directories share a common name part which is specified here. But because + * all files must be present at deploy time to be included into the Manifest + * file it makes no sense. From original comment: "Specifically, if this is + * not null (or the empty string) than any file that is used as part of the + * default directory must start with this string." + * + * @return name of a directory containing a default theme implementation */ public String getDefaultThemePath() { - String defaultThemePath = (String)get(m_defaultThemePath); + String defaultThemePath = (String) get(m_defaultThemePath); if (defaultThemePath == null || defaultThemePath.trim().length() == 0) { return null; } @@ -200,32 +217,31 @@ public class ThemeDirectorConfig extends AbstractConfig { // } // return ctx; // } - /** * This returns the name of the manifest file containing a list of default * theme. - * - * @return + * + * @return + * * @deprecated replaced by a direct copy from the default directory */ public String getDefaultThemeManifest() { - return (String)get(m_defaultThemeManifest); + return (String) get(m_defaultThemeManifest); } - - private static final String DEFAULT_THEME_URL = - ThemeDirector.DEFAULT_THEME + "." + Theme.URL; - private static final String DEFAULT_THEME_URL_ATTRIBUTE = - "defaultThemeURLAttribute"; + private static final String DEFAULT_THEME_URL = ThemeDirector.DEFAULT_THEME + + "." + Theme.URL; + private static final String DEFAULT_THEME_URL_ATTRIBUTE + = "defaultThemeURLAttribute"; /** * Purpose undocumented. - * - * @return + * + * @return */ public Collection getDownloadFileExtensions() { if (m_downloadFileExtensions == null) { - String extensions = (String)get(m_fileExtParam); + String extensions = (String) get(m_fileExtParam); if (extensions != null) { extensions = StringUtils.stripWhiteSpace(extensions.trim()); String[] extArray = StringUtils.split(extensions, ' '); @@ -237,79 +253,89 @@ public class ThemeDirectorConfig extends AbstractConfig { return m_downloadFileExtensions; } - - /** + /** * Purpose undocumented. - * + * * @param req + * * @return */ - public static String getDefaultThemeURL( HttpServletRequest req ) { - String themeURL = (String) req.getAttribute( DEFAULT_THEME_URL_ATTRIBUTE ); - if( null != themeURL ) return themeURL; + public static String getDefaultThemeURL(HttpServletRequest req) { + String themeURL = (String) req.getAttribute(DEFAULT_THEME_URL_ATTRIBUTE); + if (null != themeURL) { + return themeURL; + } - DataCollection themeApps = SessionManager.getSession().retrieve - ( ThemeDirector.BASE_DATA_OBJECT_TYPE ); - themeApps.addPath( DEFAULT_THEME_URL ); + DataCollection themeApps = SessionManager.getSession().retrieve( + ThemeDirector.BASE_DATA_OBJECT_TYPE); + themeApps.addPath(DEFAULT_THEME_URL); - if( themeApps.next() ) { - themeURL = (String) themeApps.get( DEFAULT_THEME_URL ); + if (themeApps.next()) { + themeURL = (String) themeApps.get(DEFAULT_THEME_URL); - if( s_log.isDebugEnabled() ) { - s_log.debug( "Theme app: " + themeApps.get( "id" ) ); - s_log.debug( "Default Theme URL: " + themeApps.get( DEFAULT_THEME_URL ) ); + if (s_log.isDebugEnabled()) { + s_log.debug("Theme app: " + themeApps.get("id")); + s_log.debug("Default Theme URL: " + themeApps.get( + DEFAULT_THEME_URL)); } - if( themeApps.next() ) { - s_log.warn( "There appear to be multiple themes applications loaded" ); + if (themeApps.next()) { + s_log.warn( + "There appear to be multiple themes applications loaded"); themeApps.close(); } } else { - s_log.warn( "There doesn't appear to be a themes application loaded" ); + s_log.warn("There doesn't appear to be a themes application loaded"); } - req.setAttribute( DEFAULT_THEME_URL_ATTRIBUTE, themeURL ); + req.setAttribute(DEFAULT_THEME_URL_ATTRIBUTE, themeURL); return themeURL; } - /** - * The number of seconds to wait before checking the database - * for the first time. A value of 0 means that the thread - * should not be started. This checks for published files. - * - * @return + * The number of seconds to wait before checking the database for the first + * time. A value of 0 means that the thread should not be started. This + * checks for published files. + * + * @return */ public Integer getThemePubFileWatchStartupDelay() { - return (Integer)get(m_themePubFileWatchStartupDelay); + return (Integer) get(m_themePubFileWatchStartupDelay); } /** - * Returns the number of seconds between checking for updated - * files in the file system. This checks for published files. - * - * @return + * Returns the number of seconds between checking for updated files in the + * file system. This checks for published files. + * + * @return */ public Integer getThemePubFileWatchPollDelay() { - return (Integer)get(m_themePubFileWatchPollDelay); + return (Integer) get(m_themePubFileWatchPollDelay); } /** - * The number of seconds to wait before checking the database - * for the first time. A value of 0 means that the thread - * should not be started. This checks for development files. - * @return + * The number of seconds to wait before checking the database for the first + * time. A value of 0 means that the thread should not be started. This + * checks for development files. + * + * @return */ public Integer getThemeDevFileWatchStartupDelay() { - return (Integer)get(m_themeDevFileWatchStartupDelay); + return (Integer) get(m_themeDevFileWatchStartupDelay); } /** - * Returns the number of seconds between checking for updated - * files in the file system. This checks for development files. - * @return + * Returns the number of seconds between checking for updated files in the + * file system. This checks for development files. + * + * @return */ public Integer getThemeDevFileWatchPollDelay() { - return (Integer)get(m_themeDevFileWatchPollDelay); + return (Integer) get(m_themeDevFileWatchPollDelay); } + + public String[] getLessFiles() { + return (String[]) get(m_lessFilesParam); + } + } diff --git a/ccm-themedirector/src/com/arsdigita/themedirector/ThemeDirectorConfig_parameter.properties b/ccm-themedirector/src/com/arsdigita/themedirector/ThemeDirectorConfig_parameter.properties index 17b04867a..a35139e86 100755 --- a/ccm-themedirector/src/com/arsdigita/themedirector/ThemeDirectorConfig_parameter.properties +++ b/ccm-themedirector/src/com/arsdigita/themedirector/ThemeDirectorConfig_parameter.properties @@ -38,3 +38,7 @@ themedirector.theme_pub_file_watch_poll_delay.purpose=This is the number of seco themedirector.theme_pub_file_watch_poll_delay.example=[300] themedirector.theme_pub_file_watch_poll_delay.format=[integer] +themedirectory.less_files.compile.title=Theme relative paths of LESS files to compile. +themedirectory.less_files.compile.purpose=Names of the LESS files to compile to CSS. Path is relative to theme dirctory. +themedirectory.less_files.compile.example=style/styles.less +themedirectory.less_files.compile.format=[string] \ No newline at end of file diff --git a/ccm-themedirector/src/com/arsdigita/themedirector/util/ThemeFileUtil.java b/ccm-themedirector/src/com/arsdigita/themedirector/util/ThemeFileUtil.java index 8d7113b78..1d958e4a6 100755 --- a/ccm-themedirector/src/com/arsdigita/themedirector/util/ThemeFileUtil.java +++ b/ccm-themedirector/src/com/arsdigita/themedirector/util/ThemeFileUtil.java @@ -15,12 +15,14 @@ package com.arsdigita.themedirector.util; import com.arsdigita.themedirector.Theme; +import com.arsdigita.themedirector.ThemeDirectorConfig; import com.arsdigita.themedirector.ThemeFile; import com.arsdigita.themedirector.ThemeFileCollection; import com.arsdigita.util.Assert; import com.arsdigita.util.UncheckedWrapperException; import com.inet.lib.less.Less; +import com.inet.lib.less.LessException; import org.apache.log4j.Logger; import java.util.Date; @@ -35,12 +37,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.io.InputStreamReader; - -import javax.script.Invocable; -import javax.script.ScriptEngine; -import javax.script.ScriptEngineManager; -import javax.script.ScriptException; +import java.util.ArrayList; +import java.util.List; /** * This is a utility class that is able to take a theme and, when necessary, @@ -109,7 +107,7 @@ public class ThemeFileUtil { /** * Prepares the theme directory. At the moment only compiles LESS CSS files. - * All files which do not end with {@code *.less}. Also files ending with + * All files which do end with {@code *.less}. Also files ending with * {@code *.inc.less}. are ignored. These files are interpreted as include * files that are included into another LESS file. * @@ -123,75 +121,58 @@ public class ThemeFileUtil { return; } + final List eligiblePaths = new ArrayList<>(); + for (final String lessFileName : ThemeDirectorConfig.getInstance() + .getLessFiles()) { + if (lessFileName.startsWith("/")) { + eligiblePaths.add(root.resolve(lessFileName.substring(1))); + } else { + eligiblePaths.add(root.resolve(lessFileName)); + } + } + try { Files .newDirectoryStream(root) - .forEach(path -> prepareThemeFile(path)); + .forEach(path -> prepareThemeFile(path, eligiblePaths)); } catch (IOException ex) { throw new UncheckedWrapperException(ex); } } - private static void prepareThemeFile(final Path path) { + private static void prepareThemeFile(final Path path, + final List eligiablePaths) { if (Files.isDirectory(path)) { try { Files .newDirectoryStream(path) - .forEach(current -> prepareThemeFile(current)); + .forEach(current -> prepareThemeFile(current, + eligiablePaths)); } catch (IOException ex) { throw new UncheckedWrapperException(ex); } } else { final String fileName = path.toString(); if (fileName.toLowerCase().endsWith(".less") - && !fileName.toLowerCase().endsWith(".inc.less")) { + && !fileName.toLowerCase().endsWith(".inc.less") + && !path.getFileName().toString().startsWith(".") + && eligiablePaths.contains(path)) { try { final String result = Less.compile(path.toFile(), false); final String outputPath = String.format( "%s.css", - fileName.substring(0, fileName.length() - ".less" - .length())); + fileName.substring(0, + fileName.length() - ".less".length())); final Path output = Paths.get(outputPath); Files.write(output, result.getBytes()); - } catch (IOException ex) { - throw new UncheckedWrapperException(ex); + } catch (IOException | LessException ex) { + throw new UncheckedWrapperException( + String.format("Error while compiling file \"%s\".", + fileName), + ex); } -// s_log.debug(String.format("Compiling %s to CSS...", fileName)); -// final ScriptEngine scriptEngine = new ScriptEngineManager() -// .getEngineByName("JavaScript"); -// try { -// scriptEngine.eval("var global = this;\n" -// + "var window = this;\n" -// + "var process = {env:{}};\n" + "\n" -// + "var console = {};\n" -// + "console.debug = print;\n" -// + "console.log = print;\n" -// + "console.warn = print;\n" -// + "console.error = print;"); -// scriptEngine -// .eval(new InputStreamReader(ThemeFileUtil.class -// .getResourceAsStream( -// "/com/arsdigita/themedirector/util/less.min.js"))); -// } catch (ScriptException ex) { -// throw new UncheckedWrapperException(ex); -// } -// final Invocable invocable = (Invocable) scriptEngine; -// final String lesscss; -// try { -// lesscss = new String(Files.readAllBytes(path)); -// } catch (IOException ex) { -// throw new UncheckedWrapperException(ex); -// } -// -// try { -// final Object result = invocable.invokeFunction("render", -// lesscss); -// s_log.debug(result); -// } catch (NoSuchMethodException | ScriptException ex) { -// throw new UncheckedWrapperException(ex); -// } } } }