001/** 002 * Copyright 2005-2018 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.rice.krad.theme; 017 018import org.apache.commons.lang.StringUtils; 019import org.apache.log4j.Logger; 020import org.kuali.common.util.Assert; 021import org.kuali.common.util.execute.Executable; 022import org.kuali.rice.krad.theme.postprocessor.ThemeCssFilesProcessor; 023import org.kuali.rice.krad.theme.postprocessor.ThemeFilesProcessor; 024import org.kuali.rice.krad.theme.postprocessor.ThemeJsFilesProcessor; 025import org.kuali.rice.krad.theme.preprocessor.ThemePreProcessor; 026import org.kuali.rice.krad.theme.util.NonHiddenDirectoryFilter; 027import org.kuali.rice.krad.theme.util.ThemeBuilderConstants; 028import org.kuali.rice.krad.theme.util.ThemeBuilderUtils; 029 030import java.io.File; 031import java.io.IOException; 032import java.util.ArrayList; 033import java.util.Arrays; 034import java.util.HashMap; 035import java.util.List; 036import java.util.Map; 037import java.util.Properties; 038 039/** 040 * Class that gets executed from the Spring context to build out view themes. 041 * 042 * <p>A view theme is a collection of assets that provides the base css and js for one or more views (see 043 * {@link org.kuali.rice.krad.uif.view.ViewTheme}. The theme builder provides utilities for creating and 044 * configuring themes that follow a standard directory convention.</p> 045 * 046 * <p>By default, the theme builder processes any directories under '/themes' as theme directories. Other 047 * theme directories can be added through the property {@link #getAdditionalThemeDirectories()} 048 * 049 * The basic functions provided by the theme builder are: 050 * 051 * <ul> 052 * <li>Overlay assets from a parent theme directory (if a parent is configured). Only assets that exist in 053 * the parent directory but not in the child will be overlaid</li> 054 * <li>Applies one or more configured {@link ThemePreProcessor} instances to the theme files. For example, Less 055 * files are compiled to CSS files here by the {@link org.kuali.rice.krad.theme.preprocessor.LessThemePreProcessor}</li> 056 * <li>Collects JS and CSS resources for the theme. This includes bringing in plugin resources and base KRAD script. 057 * Resources can be filtered and ordered as needed</li> 058 * <li>Perform merging and minification for each file type. During this the file types can perform additional 059 * processing (for example, URL rewriting is done for CSS files)</li> 060 * </ul> 061 * 062 * To just perform the first step (overlay parent assets), the property {@link #isSkipThemeProcessing()} can be set to 063 * true. This is useful in development where an update to a parent file just needs pushed to the output directory.</p> 064 * 065 * @author Kuali Rice Team (rice.collab@kuali.org) 066 * @see org.kuali.rice.krad.theme.ThemeBuilderOverlays 067 * @see org.kuali.rice.krad.theme.preprocessor.ThemePreProcessor 068 * @see org.kuali.rice.krad.theme.postprocessor.ThemeFilesProcessor 069 */ 070public class ThemeBuilder implements Executable { 071 private static final Logger LOG = Logger.getLogger(ThemeBuilder.class); 072 073 private String webappSourceDir; 074 private String themeBuilderOutputDir; 075 076 private List<String> themeExcludes; 077 078 private List<String> additionalThemeDirectories; 079 private List<String> additionalPluginDirectories; 080 081 private String projectVersion; 082 083 private List<ThemePreProcessor> themePreProcessors; 084 085 private Map<String, String> themeNamePathMapping; 086 private Map<String, Properties> themeNamePropertiesMapping; 087 088 private Map<String, String> pluginNamePathMapping; 089 090 private boolean skipThemeProcessing; 091 092 /** 093 * Invoked from the spring context to execute the theme builder. 094 * 095 * <p> 096 * Invokes processing of the main theme builder functions, this includes: 097 * 098 * <ul> 099 * <li>Copying assets from web source directory to the output directory</li> 100 * <li>Retrieving all theme and plugin directories, then setting up convenience maps for acquiring paths</li> 101 * <li>Iterating through each theme that should be built (those not excluded with {@link #getThemeExcludes()})</li> 102 * <li>For each theme, invoking parent and additional directory overlays, then finally calling a helper method 103 * to process the theme assets</li> 104 * </ul> 105 * </p> 106 * 107 * <p> 108 * To just perform copying of the web assets, and parent/additional directory overlays, set the property 109 * {@link #isSkipThemeProcessing()} to true 110 * </p> 111 */ 112 @Override 113 public void execute() { 114 Assert.hasText(this.webappSourceDir, "Webapp source directory not set"); 115 116 LOG.info("View builder executed on " + this.webappSourceDir); 117 118 try { 119 ThemeBuilderOverlays.copyAssetsToWorkingDir(this.webappSourceDir, this.themeBuilderOutputDir, 120 this.additionalThemeDirectories, this.additionalPluginDirectories); 121 } catch (IOException e) { 122 throw new RuntimeException("Unable to copy assets to working directory", e); 123 } 124 125 List<File> themeDirectories = getThemeDirectories(); 126 List<File> pluginDirectories = getPluginDirectories(); 127 128 // build mappings for convenient access 129 try { 130 buildMappings(themeDirectories, pluginDirectories); 131 } catch (IOException e) { 132 throw new RuntimeException("Unable to build theme mappings", e); 133 } 134 135 // themes must be ordered so that we build the parents first, and therefore they have all their files 136 // for overlaying to a child theme 137 List<String> orderedThemes = orderThemesForBuilding(); 138 139 if (this.themeExcludes != null) { 140 for (String themeToExclude : themeExcludes) { 141 themeToExclude = themeToExclude.toLowerCase(); 142 143 if (orderedThemes.contains(themeToExclude)) { 144 orderedThemes.remove(themeToExclude); 145 } 146 147 if (LOG.isDebugEnabled()) { 148 LOG.debug("Skipping build for theme " + themeToExclude); 149 } 150 } 151 } 152 153 // note important that two iterations be done over the themes and not one, all the parent 154 // and plugin assets need to be overlaid before processing is done on a theme 155 for (String themeName : orderedThemes) { 156 copyParentThemeConfig(themeName); 157 158 Properties themeProperties = this.themeNamePropertiesMapping.get(themeName); 159 160 String themePath = this.themeNamePathMapping.get(themeName); 161 File themeDirectory = new File(themePath); 162 163 ThemeBuilderOverlays.overlayParentAssets(themeName, themeDirectory, themeProperties, 164 this.themeNamePathMapping); 165 166 ThemeBuilderOverlays.overlayAdditionalDirs(themeDirectory, themeProperties, this.webappSourceDir, 167 this.themeBuilderOutputDir); 168 } 169 170 if (this.skipThemeProcessing) { 171 LOG.info("Skipping theme processing"); 172 173 return; 174 } 175 176 for (String themeName : orderedThemes) { 177 processThemeAssets(themeName); 178 } 179 } 180 181 /** 182 * Retrieves the directories that should be processed as themes. 183 * 184 * <p> 185 * By default all directories in '/themes' are included as theme directories. Additional directories can 186 * be included by setting {@link #getAdditionalThemeDirectories()} 187 * </p> 188 * 189 * @return list of file objects pointing to the theme directories 190 */ 191 protected List<File> getThemeDirectories() { 192 List<File> themeDirectories = new ArrayList<File>(); 193 194 String defaultThemesDirectoryPath = 195 this.themeBuilderOutputDir + ThemeBuilderConstants.DEFAULT_THEMES_DIRECTORY; 196 197 File defaultThemesDirectory = new File(defaultThemesDirectoryPath); 198 File[] defaultThemeDirectories = defaultThemesDirectory.listFiles(new NonHiddenDirectoryFilter()); 199 200 if (defaultThemeDirectories != null) { 201 themeDirectories = Arrays.asList(defaultThemeDirectories); 202 } 203 204 if (this.additionalThemeDirectories != null) { 205 List<File> additionalThemeDirs = ThemeBuilderUtils.getSubDirectories(new File(this.themeBuilderOutputDir), 206 this.additionalThemeDirectories); 207 themeDirectories.addAll(additionalThemeDirs); 208 } 209 210 ThemeBuilderUtils.validateFileExistence(themeDirectories, "Invalid theme directory."); 211 212 if (LOG.isDebugEnabled()) { 213 LOG.debug("Found theme directories: " + StringUtils.join(themeDirectories, ",")); 214 } 215 216 return themeDirectories; 217 } 218 219 /** 220 * Retrieves the directories that should be processed as plugins. 221 * 222 * <p> 223 * By default all directories in '/plugins' are included as plugins. Additional directories can 224 * be included by setting {@link #getAdditionalPluginDirectories()} 225 * </p> 226 * 227 * @return list of file objects pointing to the plugin directories 228 */ 229 protected List<File> getPluginDirectories() { 230 List<File> pluginDirectories = new ArrayList<File>(); 231 232 String defaultPluginsDirectoryPath = 233 this.themeBuilderOutputDir + ThemeBuilderConstants.DEFAULT_PLUGINS_DIRECTORY; 234 File defaultPluginsDirectory = new File(defaultPluginsDirectoryPath); 235 236 File[] pluginDirs = defaultPluginsDirectory.listFiles(new NonHiddenDirectoryFilter()); 237 238 if (pluginDirs != null) { 239 pluginDirectories = Arrays.asList(pluginDirs); 240 } 241 242 if (this.additionalPluginDirectories != null) { 243 List<File> additionalPluginDirs = ThemeBuilderUtils.getSubDirectories(new File(this.themeBuilderOutputDir), 244 this.additionalPluginDirectories); 245 pluginDirectories.addAll(additionalPluginDirs); 246 } 247 248 ThemeBuilderUtils.validateFileExistence(pluginDirectories, "Invalid plugin directory."); 249 250 return pluginDirectories; 251 } 252 253 /** 254 * Builds convenience maps (theme name to path map, theme name to properties mapping, and plugin 255 * name to path mapping) for the given theme and plugin directories. 256 * 257 * @param themeDirectories list of theme directories to build mappings for 258 * @param pluginDirectories list of file directories to build mappings for 259 * @throws IOException 260 */ 261 protected void buildMappings(List<File> themeDirectories, List<File> pluginDirectories) throws IOException { 262 if (LOG.isDebugEnabled()) { 263 LOG.debug("Building mappings"); 264 } 265 266 this.themeNamePathMapping = new HashMap<String, String>(); 267 this.themeNamePropertiesMapping = new HashMap<String, Properties>(); 268 269 for (File themeDirectory : themeDirectories) { 270 String themeName = themeDirectory.getName().toLowerCase(); 271 272 this.themeNamePathMapping.put(themeName, themeDirectory.getPath()); 273 274 Properties themeProperties = ThemeBuilderUtils.retrieveThemeProperties(themeDirectory.getPath()); 275 if (themeProperties == null) { 276 themeProperties = new Properties(); 277 } 278 279 this.themeNamePropertiesMapping.put(themeName, themeProperties); 280 } 281 282 this.pluginNamePathMapping = new HashMap<String, String>(); 283 284 for (File pluginDirectory : pluginDirectories) { 285 String pluginName = pluginDirectory.getName().toLowerCase(); 286 287 this.pluginNamePathMapping.put(pluginName, pluginDirectory.getPath()); 288 } 289 } 290 291 /** 292 * Builds a list containing theme names in the order for which they should be processed. 293 * 294 * <p> 295 * For the parent overlays to work correctly, the parent must be processed before the child. There can 296 * be multiple parents in the hierarchy, so here we go through and figure out the correct order 297 * </p> 298 * 299 * @return list of ordered theme names 300 */ 301 protected List<String> orderThemesForBuilding() { 302 if (LOG.isDebugEnabled()) { 303 LOG.debug("Ordering themes for building"); 304 } 305 306 List<String> orderedThemes = new ArrayList<String>(); 307 308 for (String themeName : this.themeNamePathMapping.keySet()) { 309 String themePath = this.themeNamePathMapping.get(themeName); 310 311 if (orderedThemes.contains(themeName)) { 312 continue; 313 } 314 315 List<String> themeParents = getAllThemeParents(themeName, new ArrayList<String>()); 316 for (String themeParent : themeParents) { 317 if (!orderedThemes.contains(themeParent)) { 318 orderedThemes.add(themeParent); 319 } 320 } 321 322 orderedThemes.add(themeName); 323 } 324 325 return orderedThemes; 326 } 327 328 /** 329 * Gets all parents (ancestors) for the given theme name. 330 * 331 * <p> 332 * The parent for a theme is determined by retrieving the theme's properties file, then pulling the 333 * property with key 'parent'. Then the properties file for the parent theme is pulled and check to see if 334 * it has a parent. So on until a theme is reached that does not have a parent 335 * </p> 336 * 337 * @param themeName name of theme to retrieve parents for 338 * @param themeParents list of parents that have been previously found (used to find circular references) 339 * @return list of theme names that are parents to the given theme 340 */ 341 protected List<String> getAllThemeParents(String themeName, List<String> themeParents) { 342 Properties themeProperties = this.themeNamePropertiesMapping.get(themeName); 343 if (themeProperties.containsKey(ThemeBuilderConstants.ThemeConfiguration.PARENT)) { 344 String parentThemeName = themeProperties.getProperty(ThemeBuilderConstants.ThemeConfiguration.PARENT); 345 346 if (StringUtils.isBlank(parentThemeName)) { 347 return themeParents; 348 } 349 350 if (!this.themeNamePropertiesMapping.containsKey(parentThemeName)) { 351 throw new RuntimeException("Invalid theme name for parent property: " + parentThemeName); 352 } 353 354 if (themeParents.contains(parentThemeName)) { 355 throw new RuntimeException("Circular reference found for parent: " + parentThemeName); 356 } 357 358 themeParents.addAll(getAllThemeParents(parentThemeName, themeParents)); 359 360 themeParents.add(parentThemeName); 361 } 362 363 return themeParents; 364 } 365 366 /** 367 * If the given theme has a parent, retrieve the theme properties (if exists) for the parent, 368 * then for each config property copy the parent value to the child theme properties if missing 369 * 370 * @param themeName name of the theme to pull parent config for and copy 371 */ 372 protected void copyParentThemeConfig(String themeName) { 373 Properties themeProperties = this.themeNamePropertiesMapping.get(themeName); 374 375 if (!themeProperties.containsKey(ThemeBuilderConstants.ThemeConfiguration.PARENT)) { 376 return; 377 } 378 379 String parentThemeName = themeProperties.getProperty(ThemeBuilderConstants.ThemeConfiguration.PARENT); 380 Properties parentThemeProperties = this.themeNamePropertiesMapping.get(parentThemeName); 381 382 String[] propertiesToCopy = new String[] {ThemeBuilderConstants.ThemeConfiguration.LESS_INCLUDES, 383 ThemeBuilderConstants.ThemeConfiguration.LESS_EXCLUDES, 384 ThemeBuilderConstants.ThemeConfiguration.PLUGIN_INCLUDES, 385 ThemeBuilderConstants.ThemeConfiguration.PLUGIN_EXCLUDES, 386 ThemeBuilderConstants.ThemeConfiguration.PLUGIN_FILE_EXCLUDES, 387 ThemeBuilderConstants.ThemeConfiguration.ADDITIONAL_OVERLAYS, 388 ThemeBuilderConstants.ThemeConfiguration.CSS_LOAD_FIRST, 389 ThemeBuilderConstants.ThemeConfiguration.CSS_LOAD_LAST, 390 ThemeBuilderConstants.ThemeConfiguration.PLUGIN_JS_LOAD_ORDER, 391 ThemeBuilderConstants.ThemeConfiguration.PLUGIN_CSS_LOAD_ORDER, 392 ThemeBuilderConstants.ThemeConfiguration.THEME_JS_LOAD_ORDER, 393 ThemeBuilderConstants.ThemeConfiguration.THEME_CSS_LOAD_ORDER, 394 ThemeBuilderConstants.ThemeConfiguration.JS_LOAD_FIRST, 395 ThemeBuilderConstants.ThemeConfiguration.JS_LOAD_LAST, 396 ThemeBuilderConstants.ThemeConfiguration.DEV_JS_INCLUDES}; 397 398 for (String propertyKey : propertiesToCopy) { 399 ThemeBuilderUtils.copyProperty(propertyKey, parentThemeProperties, themeProperties); 400 } 401 } 402 403 /** 404 * Performs the various steps to process the given theme 405 * 406 * <p> 407 * The theme is processed first by applying any configured {@link org.kuali.rice.krad.theme.preprocessor.ThemePreProcessor} 408 * instances (such as less processing). Once the pre processors are applied, the CSS and JS post processors are 409 * then invoked to do the final processing 410 * 411 * After processing is complete the 'theme-derived.properties' file gets written to the theme directory, which 412 * contains all the properties for the theme (set, inherited, derived) 413 * </p> 414 * 415 * @param themeName name of the theme to process 416 */ 417 protected void processThemeAssets(String themeName) { 418 Properties themeProperties = this.themeNamePropertiesMapping.get(themeName); 419 420 String themePath = this.themeNamePathMapping.get(themeName); 421 File themeDirectory = new File(themePath); 422 423 LOG.info("Processing assets for theme: " + themeName); 424 425 // apply pre-processors which can modify the theme assets before they are collected 426 if (this.themePreProcessors != null) { 427 for (ThemePreProcessor preProcessor : this.themePreProcessors) { 428 preProcessor.processTheme(themeName, themeDirectory, themeProperties); 429 } 430 } 431 432 // apply processors for CSS and JS files to do final processing 433 File workingDir = new File(this.themeBuilderOutputDir); 434 435 Map<String, File> themePluginDirsMap = collectThemePluginDirs(themeProperties); 436 437 ThemeFilesProcessor filesProcessor = new ThemeCssFilesProcessor(themeName, themeDirectory, themeProperties, 438 themePluginDirsMap, workingDir, this.projectVersion); 439 filesProcessor.process(); 440 441 filesProcessor = new ThemeJsFilesProcessor(themeName, themeDirectory, themeProperties, 442 themePluginDirsMap, workingDir, this.projectVersion); 443 filesProcessor.process(); 444 445 try { 446 ThemeBuilderUtils.storeThemeProperties(themePath, themeProperties); 447 } catch (IOException e) { 448 throw new RuntimeException("Unable to update theme.properties file", e); 449 } 450 } 451 452 /** 453 * Helper method that filters the list of all plugins and returns those that should be used 454 * with the theme 455 * 456 * <p> 457 * Which plugins to include for a theme can be configured using the pluginIncludes and pluginExlcudes 458 * property keys 459 * </p> 460 * 461 * @param themeProperties properties file for the theme, used to retrieve the plugin configuration 462 * @return map containing the plugins for the theme, map key is the plugin name and map value gives 463 * the plugin directory 464 */ 465 protected Map<String, File> collectThemePluginDirs(Properties themeProperties) { 466 Map<String, File> themePluginDirs = new HashMap<String, File>(); 467 468 String[] pluginIncludes = ThemeBuilderUtils.getPropertyValueAsArray( 469 ThemeBuilderConstants.ThemeConfiguration.PLUGIN_INCLUDES, themeProperties); 470 471 String[] pluginExcludes = ThemeBuilderUtils.getPropertyValueAsArray( 472 ThemeBuilderConstants.ThemeConfiguration.PLUGIN_EXCLUDES, themeProperties); 473 474 for (Map.Entry<String, String> pluginMapping : this.pluginNamePathMapping.entrySet()) { 475 String pluginName = pluginMapping.getKey(); 476 477 if (ThemeBuilderUtils.inExcludeList(pluginName, pluginExcludes)) { 478 continue; 479 } 480 481 if (ThemeBuilderUtils.inIncludeList(pluginName, pluginIncludes)) { 482 themePluginDirs.put(pluginName, new File(pluginMapping.getValue())); 483 } 484 } 485 486 themeProperties.put(ThemeBuilderConstants.DerivedConfiguration.THEME_PLUGIN_NAMES, 487 StringUtils.join(themePluginDirs.keySet(), ",")); 488 489 return themePluginDirs; 490 } 491 492 /** 493 * Map that associates theme names with their path, provided here for subclasses 494 * 495 * @return map of theme name/paths, map key is the theme name, map value is the theme path 496 */ 497 protected Map<String, String> getThemeNamePathMapping() { 498 return themeNamePathMapping; 499 } 500 501 /** 502 * Map that associates theme names with their properties, provided here for subclasses 503 * 504 * @return map of theme name/properties, map key is the theme name, map value is the properties object 505 */ 506 protected Map<String, Properties> getThemeNamePropertiesMapping() { 507 return themeNamePropertiesMapping; 508 } 509 510 /** 511 * Absolute path to the directory that contains the web application source 512 * 513 * <p> 514 * Generally this is the base directory for the application/module, then /src/main/webapp 515 * </p> 516 * 517 * <p> 518 * If you are using the maven plugin this can be set by the maven property <code>webapp.source.dir</code> 519 * </p> 520 * 521 * @return path to webapp source directory 522 */ 523 public String getWebappSourceDir() { 524 return webappSourceDir; 525 } 526 527 /** 528 * Setter for the path to the webapp source 529 * 530 * @param webappSourceDir 531 */ 532 public void setWebappSourceDir(String webappSourceDir) { 533 if (StringUtils.isNotBlank(webappSourceDir)) { 534 // trim off any trailing path separators 535 if (webappSourceDir.endsWith(File.separator) || webappSourceDir.endsWith("/")) { 536 webappSourceDir = webappSourceDir.substring(0, webappSourceDir.length() - 1); 537 } 538 } 539 540 this.webappSourceDir = webappSourceDir; 541 } 542 543 /** 544 * Absolute path to the directory the theme builder should output content to 545 * 546 * <p> 547 * Generally this will be the output directory for the exploded war being created. However you can also 548 * choose to output to a temporary directory, then copy the assets over at a later phase 549 * </p> 550 * 551 * <p> 552 * If you are using the maven plugin this can be set by the maven property <code>theme.builder.output.dir</code> 553 * </p> 554 * 555 * @return path to the output directory 556 */ 557 public String getThemeBuilderOutputDir() { 558 return themeBuilderOutputDir; 559 } 560 561 /** 562 * Setter for the path to the output directory 563 * 564 * @param themeBuilderOutputDir 565 */ 566 public void setThemeBuilderOutputDir(String themeBuilderOutputDir) { 567 if (StringUtils.isNotBlank(themeBuilderOutputDir)) { 568 // trim off any trailing path separators 569 if (themeBuilderOutputDir.endsWith(File.separator) || themeBuilderOutputDir.endsWith("/")) { 570 themeBuilderOutputDir = themeBuilderOutputDir.substring(0, themeBuilderOutputDir.length() - 1); 571 } 572 } 573 574 this.themeBuilderOutputDir = themeBuilderOutputDir; 575 } 576 577 /** 578 * List of theme names that should be excluded from theme processing 579 * 580 * <p> 581 * Directories for themes that are excluded will be copied to the output directory but no further 582 * processing will occur on that theme. 583 * </p> 584 * 585 * <p> 586 * If your web application receives web overlays which include themes, they will already be processed. 587 * Processing them again will result in duplicate content. Therefore you should exclude these themes using 588 * this property 589 * </p> 590 * 591 * <p> 592 * If you are using the maven plugin this can be set by the maven property <code>theme.builder.excludes</code> 593 * </p> 594 * 595 * @return list of excluded theme names 596 */ 597 public List<String> getThemeExcludes() { 598 return themeExcludes; 599 } 600 601 /** 602 * Setter for the list of theme names to exclude from processing 603 * 604 * @param themeExcludes 605 */ 606 public void setThemeExcludes(List<String> themeExcludes) { 607 this.themeExcludes = themeExcludes; 608 } 609 610 /** 611 * Convenience setter that takes a string and parses to populate the theme excludes list 612 * 613 * @param themeExcludes string containing theme names to exclude which are delimited using a comma 614 */ 615 public void setThemeExcludesStr(String themeExcludes) { 616 if (StringUtils.isNotBlank(themeExcludes)) { 617 String[] themeExcludesArray = themeExcludes.split(","); 618 this.themeExcludes = Arrays.asList(themeExcludesArray); 619 } 620 } 621 622 /** 623 * List of absolute paths to include as additional theme directories 624 * 625 * <p> 626 * By default all directories under the web root folder <code>themes</code> are included. Other web 627 * directories can be processed as themes by including their path in this list 628 * </p> 629 * 630 * <p> 631 * If you are using the maven plugin this can be set by the maven property <code>theme.builder.theme.adddirs</code> 632 * </p> 633 * 634 * @return list of paths for additional themes 635 */ 636 public List<String> getAdditionalThemeDirectories() { 637 return additionalThemeDirectories; 638 } 639 640 /** 641 * Setter for the list of additional theme directory paths 642 * 643 * @param additionalThemeDirectories 644 */ 645 public void setAdditionalThemeDirectories(List<String> additionalThemeDirectories) { 646 this.additionalThemeDirectories = additionalThemeDirectories; 647 } 648 649 /** 650 * Convenience setter that takes a string and parses to populate the additional theme directories list 651 * 652 * @param additionalThemeDirectories string containing additional theme directories which are 653 * delimited using a comma 654 */ 655 public void setAdditionalThemeDirectoriesStr(String additionalThemeDirectories) { 656 if (StringUtils.isNotBlank(additionalThemeDirectories)) { 657 String[] additionalThemeDirectoriesArray = additionalThemeDirectories.split(","); 658 this.additionalThemeDirectories = Arrays.asList(additionalThemeDirectoriesArray); 659 } 660 } 661 662 /** 663 * List of absolute paths to include as additional plugin directories 664 * 665 * <p> 666 * By default all directories under the web root folder <code>plugins</code> are included. Other web 667 * directories can be processed as plugins by including their path in this list 668 * </p> 669 * 670 * <p> 671 * If you are using the maven plugin this can be set by the maven property <code>theme.builder.plugin.adddirs</code> 672 * </p> 673 * 674 * @return list of paths for additional plugins 675 */ 676 public List<String> getAdditionalPluginDirectories() { 677 return additionalPluginDirectories; 678 } 679 680 /** 681 * Setter for the list of additional plugin directory paths 682 * 683 * @param additionalPluginDirectories 684 */ 685 public void setAdditionalPluginDirectories(List<String> additionalPluginDirectories) { 686 this.additionalPluginDirectories = additionalPluginDirectories; 687 } 688 689 /** 690 * Convenience setter that takes a string and parses to populate the additional plugin directories list 691 * 692 * @param additionalPluginDirectories string containing additional plugin directories which are 693 * delimited using a comma 694 */ 695 public void setAdditionalPluginDirectoriesStr(String additionalPluginDirectories) { 696 if (StringUtils.isNotBlank(additionalPluginDirectories)) { 697 String[] additionalPluginDirectoriesArray = additionalPluginDirectories.split(","); 698 this.additionalPluginDirectories = Arrays.asList(additionalPluginDirectoriesArray); 699 } 700 } 701 702 /** 703 * Version for the project that will be used to stamp the minified file 704 * 705 * <p> 706 * In order to facilitate automatic downloads between project releases, the minified files are stamped with 707 * the version number. 708 * </p> 709 * 710 * <p> 711 * If you are using the maven plugin this can be set by the maven property <code>project.version</code> 712 * </p> 713 * 714 * @return version string for project 715 */ 716 public String getProjectVersion() { 717 return projectVersion; 718 } 719 720 /** 721 * Setter for the project version 722 * 723 * @param projectVersion 724 */ 725 public void setProjectVersion(String projectVersion) { 726 this.projectVersion = projectVersion; 727 } 728 729 /** 730 * List of {@link ThemePreProcessor} instances that should be applied to the themes 731 * 732 * @return list of pre processors to apply 733 */ 734 public List<ThemePreProcessor> getThemePreProcessors() { 735 return themePreProcessors; 736 } 737 738 /** 739 * Setter for the list of theme pre processors 740 * 741 * @param themePreProcessors 742 */ 743 public void setThemePreProcessors(List<ThemePreProcessor> themePreProcessors) { 744 this.themePreProcessors = themePreProcessors; 745 } 746 747 /** 748 * Indicates whether processing of the themes should be skipped 749 * 750 * <p> 751 * In development it can be useful to just update the output directory with the theme assets, and skip 752 * processing such as Less and minification (which can be time consuming). Setting this flag to true will 753 * skip processing of pre and post processors, just doing the overlay. By default this is false 754 * </p> 755 * 756 * @return true if theme processing should be skipped, false if not 757 */ 758 public boolean isSkipThemeProcessing() { 759 return skipThemeProcessing; 760 } 761 762 /** 763 * Setter to skip theme processing 764 * 765 * @param skipThemeProcessing 766 */ 767 public void setSkipThemeProcessing(boolean skipThemeProcessing) { 768 this.skipThemeProcessing = skipThemeProcessing; 769 } 770 771}