1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.rice.krad.theme;
17
18 import org.apache.commons.lang.StringUtils;
19 import org.apache.log4j.Logger;
20 import org.kuali.common.util.Assert;
21 import org.kuali.common.util.execute.Executable;
22 import org.kuali.rice.krad.theme.postprocessor.ThemeCssFilesProcessor;
23 import org.kuali.rice.krad.theme.postprocessor.ThemeFilesProcessor;
24 import org.kuali.rice.krad.theme.postprocessor.ThemeJsFilesProcessor;
25 import org.kuali.rice.krad.theme.preprocessor.ThemePreProcessor;
26 import org.kuali.rice.krad.theme.util.NonHiddenDirectoryFilter;
27 import org.kuali.rice.krad.theme.util.ThemeBuilderConstants;
28 import org.kuali.rice.krad.theme.util.ThemeBuilderUtils;
29
30 import java.io.File;
31 import java.io.IOException;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Properties;
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70 public class ThemeBuilder implements Executable {
71 private static final Logger LOG = Logger.getLogger(ThemeBuilder.class);
72
73 private String webappSourceDir;
74 private String themeBuilderOutputDir;
75
76 private List<String> themeExcludes;
77
78 private List<String> additionalThemeDirectories;
79 private List<String> additionalPluginDirectories;
80
81 private String projectVersion;
82
83 private List<ThemePreProcessor> themePreProcessors;
84
85 private Map<String, String> themeNamePathMapping;
86 private Map<String, Properties> themeNamePropertiesMapping;
87
88 private Map<String, String> pluginNamePathMapping;
89
90 private boolean skipThemeProcessing;
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
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
129 try {
130 buildMappings(themeDirectories, pluginDirectories);
131 } catch (IOException e) {
132 throw new RuntimeException("Unable to build theme mappings", e);
133 }
134
135
136
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
154
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
183
184
185
186
187
188
189
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
221
222
223
224
225
226
227
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
255
256
257
258
259
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
293
294
295
296
297
298
299
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
330
331
332
333
334
335
336
337
338
339
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
368
369
370
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
405
406
407
408
409
410
411
412
413
414
415
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
426 if (this.themePreProcessors != null) {
427 for (ThemePreProcessor preProcessor : this.themePreProcessors) {
428 preProcessor.processTheme(themeName, themeDirectory, themeProperties);
429 }
430 }
431
432
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
454
455
456
457
458
459
460
461
462
463
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
494
495
496
497 protected Map<String, String> getThemeNamePathMapping() {
498 return themeNamePathMapping;
499 }
500
501
502
503
504
505
506 protected Map<String, Properties> getThemeNamePropertiesMapping() {
507 return themeNamePropertiesMapping;
508 }
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523 public String getWebappSourceDir() {
524 return webappSourceDir;
525 }
526
527
528
529
530
531
532 public void setWebappSourceDir(String webappSourceDir) {
533 if (StringUtils.isNotBlank(webappSourceDir)) {
534
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
545
546
547
548
549
550
551
552
553
554
555
556
557 public String getThemeBuilderOutputDir() {
558 return themeBuilderOutputDir;
559 }
560
561
562
563
564
565
566 public void setThemeBuilderOutputDir(String themeBuilderOutputDir) {
567 if (StringUtils.isNotBlank(themeBuilderOutputDir)) {
568
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
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597 public List<String> getThemeExcludes() {
598 return themeExcludes;
599 }
600
601
602
603
604
605
606 public void setThemeExcludes(List<String> themeExcludes) {
607 this.themeExcludes = themeExcludes;
608 }
609
610
611
612
613
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
624
625
626
627
628
629
630
631
632
633
634
635
636 public List<String> getAdditionalThemeDirectories() {
637 return additionalThemeDirectories;
638 }
639
640
641
642
643
644
645 public void setAdditionalThemeDirectories(List<String> additionalThemeDirectories) {
646 this.additionalThemeDirectories = additionalThemeDirectories;
647 }
648
649
650
651
652
653
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
664
665
666
667
668
669
670
671
672
673
674
675
676 public List<String> getAdditionalPluginDirectories() {
677 return additionalPluginDirectories;
678 }
679
680
681
682
683
684
685 public void setAdditionalPluginDirectories(List<String> additionalPluginDirectories) {
686 this.additionalPluginDirectories = additionalPluginDirectories;
687 }
688
689
690
691
692
693
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
704
705
706
707
708
709
710
711
712
713
714
715
716 public String getProjectVersion() {
717 return projectVersion;
718 }
719
720
721
722
723
724
725 public void setProjectVersion(String projectVersion) {
726 this.projectVersion = projectVersion;
727 }
728
729
730
731
732
733
734 public List<ThemePreProcessor> getThemePreProcessors() {
735 return themePreProcessors;
736 }
737
738
739
740
741
742
743 public void setThemePreProcessors(List<ThemePreProcessor> themePreProcessors) {
744 this.themePreProcessors = themePreProcessors;
745 }
746
747
748
749
750
751
752
753
754
755
756
757
758 public boolean isSkipThemeProcessing() {
759 return skipThemeProcessing;
760 }
761
762
763
764
765
766
767 public void setSkipThemeProcessing(boolean skipThemeProcessing) {
768 this.skipThemeProcessing = skipThemeProcessing;
769 }
770
771 }