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
71
72
73
74 public class ThemeBuilder implements Executable {
75 private static final Logger LOG = Logger.getLogger(ThemeBuilder.class);
76
77 private String webappSourceDir;
78 private String themeBuilderOutputDir;
79
80 private List<String> themeExcludes;
81
82 private List<String> additionalThemeDirectories;
83 private List<String> additionalPluginDirectories;
84
85 private String projectVersion;
86
87 private List<ThemePreProcessor> themePreProcessors;
88
89 private Map<String, String> themeNamePathMapping;
90 private Map<String, Properties> themeNamePropertiesMapping;
91
92 private Map<String, String> pluginNamePathMapping;
93
94 private boolean skipThemeProcessing;
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116 public void execute() {
117 Assert.hasText(this.webappSourceDir, "Webapp source directory not set");
118
119 LOG.info("View builder executed on " + this.webappSourceDir);
120
121 try {
122 ThemeBuilderOverlays.copyAssetsToWorkingDir(this.webappSourceDir, this.themeBuilderOutputDir,
123 this.additionalThemeDirectories, this.additionalPluginDirectories);
124 } catch (IOException e) {
125 throw new RuntimeException("Unable to copy assets to working directory", e);
126 }
127
128 List<File> themeDirectories = getThemeDirectories();
129 List<File> pluginDirectories = getPluginDirectories();
130
131
132 try {
133 buildMappings(themeDirectories, pluginDirectories);
134 } catch (IOException e) {
135 throw new RuntimeException("Unable to build theme mappings", e);
136 }
137
138
139
140 List<String> orderedThemes = orderThemesForBuilding();
141
142 if (this.themeExcludes != null) {
143 for (String themeToExclude : themeExcludes) {
144 themeToExclude = themeToExclude.toLowerCase();
145
146 if (orderedThemes.contains(themeToExclude)) {
147 orderedThemes.remove(themeToExclude);
148 }
149
150 if (LOG.isDebugEnabled()) {
151 LOG.debug("Skipping build for theme " + themeToExclude);
152 }
153 }
154 }
155
156
157
158 for (String themeName : orderedThemes) {
159 copyParentThemeConfig(themeName);
160
161 Properties themeProperties = this.themeNamePropertiesMapping.get(themeName);
162
163 String themePath = this.themeNamePathMapping.get(themeName);
164 File themeDirectory = new File(themePath);
165
166 ThemeBuilderOverlays.overlayParentAssets(themeName, themeDirectory, themeProperties,
167 this.themeNamePathMapping);
168
169 ThemeBuilderOverlays.overlayAdditionalDirs(themeDirectory, themeProperties, this.webappSourceDir,
170 this.themeBuilderOutputDir);
171 }
172
173 if (this.skipThemeProcessing) {
174 LOG.info("Skipping theme processing");
175
176 return;
177 }
178
179 for (String themeName : orderedThemes) {
180 processThemeAssets(themeName);
181 }
182 }
183
184
185
186
187
188
189
190
191
192
193
194 protected List<File> getThemeDirectories() {
195 List<File> themeDirectories = new ArrayList<File>();
196
197 String defaultThemesDirectoryPath =
198 this.themeBuilderOutputDir + ThemeBuilderConstants.DEFAULT_THEMES_DIRECTORY;
199
200 File defaultThemesDirectory = new File(defaultThemesDirectoryPath);
201 File[] defaultThemeDirectories = defaultThemesDirectory.listFiles(new NonHiddenDirectoryFilter());
202
203 if (defaultThemeDirectories != null) {
204 themeDirectories = Arrays.asList(defaultThemeDirectories);
205 }
206
207 if (this.additionalThemeDirectories != null) {
208 List<File> additionalThemeDirs = ThemeBuilderUtils.getSubDirectories(new File(this.themeBuilderOutputDir),
209 this.additionalThemeDirectories);
210 themeDirectories.addAll(additionalThemeDirs);
211 }
212
213 ThemeBuilderUtils.validateFileExistence(themeDirectories, "Invalid theme directory.");
214
215 if (LOG.isDebugEnabled()) {
216 LOG.debug("Found theme directories: " + StringUtils.join(themeDirectories, ","));
217 }
218
219 return themeDirectories;
220 }
221
222
223
224
225
226
227
228
229
230
231
232 protected List<File> getPluginDirectories() {
233 List<File> pluginDirectories = new ArrayList<File>();
234
235 String defaultPluginsDirectoryPath =
236 this.themeBuilderOutputDir + ThemeBuilderConstants.DEFAULT_PLUGINS_DIRECTORY;
237 File defaultPluginsDirectory = new File(defaultPluginsDirectoryPath);
238
239 File[] pluginDirs = defaultPluginsDirectory.listFiles(new NonHiddenDirectoryFilter());
240
241 if (pluginDirs != null) {
242 pluginDirectories = Arrays.asList(pluginDirs);
243 }
244
245 if (this.additionalPluginDirectories != null) {
246 List<File> additionalPluginDirs = ThemeBuilderUtils.getSubDirectories(new File(this.themeBuilderOutputDir),
247 this.additionalPluginDirectories);
248 pluginDirectories.addAll(additionalPluginDirs);
249 }
250
251 ThemeBuilderUtils.validateFileExistence(pluginDirectories, "Invalid plugin directory.");
252
253 return pluginDirectories;
254 }
255
256
257
258
259
260
261
262
263
264 protected void buildMappings(List<File> themeDirectories, List<File> pluginDirectories) throws IOException {
265 if (LOG.isDebugEnabled()) {
266 LOG.debug("Building mappings");
267 }
268
269 this.themeNamePathMapping = new HashMap<String, String>();
270 this.themeNamePropertiesMapping = new HashMap<String, Properties>();
271
272 for (File themeDirectory : themeDirectories) {
273 String themeName = themeDirectory.getName().toLowerCase();
274
275 this.themeNamePathMapping.put(themeName, themeDirectory.getPath());
276
277 Properties themeProperties = ThemeBuilderUtils.retrieveThemeProperties(themeDirectory.getPath());
278 if (themeProperties == null) {
279 themeProperties = new Properties();
280 }
281
282 this.themeNamePropertiesMapping.put(themeName, themeProperties);
283 }
284
285 this.pluginNamePathMapping = new HashMap<String, String>();
286
287 for (File pluginDirectory : pluginDirectories) {
288 String pluginName = pluginDirectory.getName().toLowerCase();
289
290 this.pluginNamePathMapping.put(pluginName, pluginDirectory.getPath());
291 }
292 }
293
294
295
296
297
298
299
300
301
302
303
304 protected List<String> orderThemesForBuilding() {
305 if (LOG.isDebugEnabled()) {
306 LOG.debug("Ordering themes for building");
307 }
308
309 List<String> orderedThemes = new ArrayList<String>();
310
311 for (String themeName : this.themeNamePathMapping.keySet()) {
312 String themePath = this.themeNamePathMapping.get(themeName);
313
314 if (orderedThemes.contains(themeName)) {
315 continue;
316 }
317
318 List<String> themeParents = getAllThemeParents(themeName, new ArrayList<String>());
319 for (String themeParent : themeParents) {
320 if (!orderedThemes.contains(themeParent)) {
321 orderedThemes.add(themeParent);
322 }
323 }
324
325 orderedThemes.add(themeName);
326 }
327
328 return orderedThemes;
329 }
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344 protected List<String> getAllThemeParents(String themeName, List<String> themeParents) {
345 Properties themeProperties = this.themeNamePropertiesMapping.get(themeName);
346 if (themeProperties.containsKey(ThemeBuilderConstants.ThemeConfiguration.PARENT)) {
347 String parentThemeName = themeProperties.getProperty(ThemeBuilderConstants.ThemeConfiguration.PARENT);
348
349 if (StringUtils.isBlank(parentThemeName)) {
350 return themeParents;
351 }
352
353 if (!this.themeNamePropertiesMapping.containsKey(parentThemeName)) {
354 throw new RuntimeException("Invalid theme name for parent property: " + parentThemeName);
355 }
356
357 if (themeParents.contains(parentThemeName)) {
358 throw new RuntimeException("Circular reference found for parent: " + parentThemeName);
359 }
360
361 themeParents.addAll(getAllThemeParents(parentThemeName, themeParents));
362
363 themeParents.add(parentThemeName);
364 }
365
366 return themeParents;
367 }
368
369
370
371
372
373
374
375 protected void copyParentThemeConfig(String themeName) {
376 Properties themeProperties = this.themeNamePropertiesMapping.get(themeName);
377
378 if (!themeProperties.containsKey(ThemeBuilderConstants.ThemeConfiguration.PARENT)) {
379 return;
380 }
381
382 String parentThemeName = themeProperties.getProperty(ThemeBuilderConstants.ThemeConfiguration.PARENT);
383 Properties parentThemeProperties = this.themeNamePropertiesMapping.get(parentThemeName);
384
385 String[] propertiesToCopy = new String[] {ThemeBuilderConstants.ThemeConfiguration.LESS_INCLUDES,
386 ThemeBuilderConstants.ThemeConfiguration.LESS_EXCLUDES,
387 ThemeBuilderConstants.ThemeConfiguration.PLUGIN_INCLUDES,
388 ThemeBuilderConstants.ThemeConfiguration.PLUGIN_EXCLUDES,
389 ThemeBuilderConstants.ThemeConfiguration.PLUGIN_FILE_EXCLUDES,
390 ThemeBuilderConstants.ThemeConfiguration.ADDITIONAL_OVERLAYS,
391 ThemeBuilderConstants.ThemeConfiguration.CSS_LOAD_FIRST,
392 ThemeBuilderConstants.ThemeConfiguration.CSS_LOAD_LAST,
393 ThemeBuilderConstants.ThemeConfiguration.PLUGIN_JS_LOAD_ORDER,
394 ThemeBuilderConstants.ThemeConfiguration.PLUGIN_CSS_LOAD_ORDER,
395 ThemeBuilderConstants.ThemeConfiguration.THEME_JS_LOAD_ORDER,
396 ThemeBuilderConstants.ThemeConfiguration.THEME_CSS_LOAD_ORDER,
397 ThemeBuilderConstants.ThemeConfiguration.JS_LOAD_FIRST,
398 ThemeBuilderConstants.ThemeConfiguration.JS_LOAD_LAST};
399
400 for (String propertyKey : propertiesToCopy) {
401 ThemeBuilderUtils.copyProperty(propertyKey, parentThemeProperties, themeProperties);
402 }
403 }
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419 protected void processThemeAssets(String themeName) {
420 Properties themeProperties = this.themeNamePropertiesMapping.get(themeName);
421
422 String themePath = this.themeNamePathMapping.get(themeName);
423 File themeDirectory = new File(themePath);
424
425 LOG.info("Processing assets for theme: " + themeName);
426
427
428 if (this.themePreProcessors != null) {
429 for (ThemePreProcessor preProcessor : this.themePreProcessors) {
430 preProcessor.processTheme(themeName, themeDirectory, themeProperties);
431 }
432 }
433
434
435 File workingDir = new File(this.themeBuilderOutputDir);
436
437 Map<String, File> themePluginDirsMap = collectThemePluginDirs(themeProperties);
438
439 ThemeFilesProcessor filesProcessor = new ThemeCssFilesProcessor(themeName, themeDirectory, themeProperties,
440 themePluginDirsMap, workingDir, this.projectVersion);
441 filesProcessor.process();
442
443 filesProcessor = new ThemeJsFilesProcessor(themeName, themeDirectory, themeProperties,
444 themePluginDirsMap, workingDir, this.projectVersion);
445 filesProcessor.process();
446
447 try {
448 ThemeBuilderUtils.storeThemeProperties(themePath, themeProperties);
449 } catch (IOException e) {
450 throw new RuntimeException("Unable to update theme.properties file", e);
451 }
452 }
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467 protected Map<String, File> collectThemePluginDirs(Properties themeProperties) {
468 Map<String, File> themePluginDirs = new HashMap<String, File>();
469
470 String[] pluginIncludes = ThemeBuilderUtils.getPropertyValueAsArray(
471 ThemeBuilderConstants.ThemeConfiguration.PLUGIN_INCLUDES, themeProperties);
472
473 String[] pluginExcludes = ThemeBuilderUtils.getPropertyValueAsArray(
474 ThemeBuilderConstants.ThemeConfiguration.PLUGIN_EXCLUDES, themeProperties);
475
476 for (Map.Entry<String, String> pluginMapping : this.pluginNamePathMapping.entrySet()) {
477 String pluginName = pluginMapping.getKey();
478
479 if (ThemeBuilderUtils.inExcludeList(pluginName, pluginExcludes)) {
480 continue;
481 }
482
483 if (ThemeBuilderUtils.inIncludeList(pluginName, pluginIncludes)) {
484 themePluginDirs.put(pluginName, new File(pluginMapping.getValue()));
485 }
486 }
487
488 themeProperties.put(ThemeBuilderConstants.DerivedConfiguration.THEME_PLUGIN_NAMES,
489 StringUtils.join(themePluginDirs.keySet(), ","));
490
491 return themePluginDirs;
492 }
493
494
495
496
497
498
499 protected Map<String, String> getThemeNamePathMapping() {
500 return themeNamePathMapping;
501 }
502
503
504
505
506
507
508 protected Map<String, Properties> getThemeNamePropertiesMapping() {
509 return themeNamePropertiesMapping;
510 }
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525 public String getWebappSourceDir() {
526 return webappSourceDir;
527 }
528
529
530
531
532
533
534 public void setWebappSourceDir(String webappSourceDir) {
535 if (StringUtils.isNotBlank(webappSourceDir)) {
536
537 if (webappSourceDir.endsWith(File.separator) || webappSourceDir.endsWith("/")) {
538 webappSourceDir = webappSourceDir.substring(0, webappSourceDir.length() - 1);
539 }
540 }
541
542 this.webappSourceDir = webappSourceDir;
543 }
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559 public String getThemeBuilderOutputDir() {
560 return themeBuilderOutputDir;
561 }
562
563
564
565
566
567
568 public void setThemeBuilderOutputDir(String themeBuilderOutputDir) {
569 if (StringUtils.isNotBlank(themeBuilderOutputDir)) {
570
571 if (themeBuilderOutputDir.endsWith(File.separator) || themeBuilderOutputDir.endsWith("/")) {
572 themeBuilderOutputDir = themeBuilderOutputDir.substring(0, themeBuilderOutputDir.length() - 1);
573 }
574 }
575
576 this.themeBuilderOutputDir = themeBuilderOutputDir;
577 }
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599 public List<String> getThemeExcludes() {
600 return themeExcludes;
601 }
602
603
604
605
606
607
608 public void setThemeExcludes(List<String> themeExcludes) {
609 this.themeExcludes = themeExcludes;
610 }
611
612
613
614
615
616
617 public void setThemeExcludesStr(String themeExcludes) {
618 if (StringUtils.isNotBlank(themeExcludes)) {
619 String[] themeExcludesArray = themeExcludes.split(",");
620 this.themeExcludes = Arrays.asList(themeExcludesArray);
621 }
622 }
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638 public List<String> getAdditionalThemeDirectories() {
639 return additionalThemeDirectories;
640 }
641
642
643
644
645
646
647 public void setAdditionalThemeDirectories(List<String> additionalThemeDirectories) {
648 this.additionalThemeDirectories = additionalThemeDirectories;
649 }
650
651
652
653
654
655
656
657 public void setAdditionalThemeDirectoriesStr(String additionalThemeDirectories) {
658 if (StringUtils.isNotBlank(additionalThemeDirectories)) {
659 String[] additionalThemeDirectoriesArray = additionalThemeDirectories.split(",");
660 this.additionalThemeDirectories = Arrays.asList(additionalThemeDirectoriesArray);
661 }
662 }
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678 public List<String> getAdditionalPluginDirectories() {
679 return additionalPluginDirectories;
680 }
681
682
683
684
685
686
687 public void setAdditionalPluginDirectories(List<String> additionalPluginDirectories) {
688 this.additionalPluginDirectories = additionalPluginDirectories;
689 }
690
691
692
693
694
695
696
697 public void setAdditionalPluginDirectoriesStr(String additionalPluginDirectories) {
698 if (StringUtils.isNotBlank(additionalPluginDirectories)) {
699 String[] additionalPluginDirectoriesArray = additionalPluginDirectories.split(",");
700 this.additionalPluginDirectories = Arrays.asList(additionalPluginDirectoriesArray);
701 }
702 }
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718 public String getProjectVersion() {
719 return projectVersion;
720 }
721
722
723
724
725
726
727 public void setProjectVersion(String projectVersion) {
728 this.projectVersion = projectVersion;
729 }
730
731
732
733
734
735
736 public List<ThemePreProcessor> getThemePreProcessors() {
737 return themePreProcessors;
738 }
739
740
741
742
743
744
745 public void setThemePreProcessors(List<ThemePreProcessor> themePreProcessors) {
746 this.themePreProcessors = themePreProcessors;
747 }
748
749
750
751
752
753
754
755
756
757
758
759
760 public boolean isSkipThemeProcessing() {
761 return skipThemeProcessing;
762 }
763
764
765
766
767
768
769 public void setSkipThemeProcessing(boolean skipThemeProcessing) {
770 this.skipThemeProcessing = skipThemeProcessing;
771 }
772
773 }