001/* 002 * Copyright 2010 Anyware Services 003 * 004 * Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0 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.ametys.web.skin; 017 018import java.io.InputStream; 019import java.nio.file.Files; 020import java.nio.file.Path; 021import java.util.ArrayList; 022import java.util.Date; 023import java.util.HashMap; 024import java.util.LinkedHashMap; 025import java.util.List; 026import java.util.Map; 027 028import org.apache.avalon.framework.configuration.Configuration; 029import org.apache.avalon.framework.configuration.ConfigurationException; 030import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 031import org.apache.commons.lang.StringUtils; 032import org.slf4j.Logger; 033import org.slf4j.LoggerFactory; 034 035import org.ametys.runtime.i18n.I18nizableText; 036 037 038/** 039 * A skin 040 * Do not mistake with {@link SkinParametersModel} 041 */ 042public class SkinModel 043{ 044 private static Logger _logger = LoggerFactory.getLogger(SkinModel.class); 045 046 /** The skin id (e.g. the directory name) */ 047 protected String _id; 048 /** The skin directory */ 049 protected Path _file; 050 /** The skin name */ 051 protected I18nizableText _label; 052 /** The skin description */ 053 protected I18nizableText _description; 054 /** The skin thumbnail 16x16 */ 055 protected String _smallImage; 056 /** The skin thumbnail 32x32 */ 057 protected String _mediumImage; 058 /** The skin thumbnail 48x48 */ 059 protected String _largeImage; 060 061 /** The last time the file was loaded */ 062 protected long _lastConfUpdate; 063 /** Is the skin modifiable */ 064 protected boolean _modifiable; 065 066 private long _lastDefaultValuesUpdate; 067 private Map<String, String> _defaultValues; 068 private String _defaultColorTheme; 069 070 private long _lastColorsUpdate; 071 private Map<String, Theme> _themes = new HashMap<>(); 072 private List<String> _colors = new ArrayList<>(); 073 074 private long _lastCssStylesUpdate; 075 private Map<String, List<CssMenuItem>> _cssStyles = new HashMap<>(); 076 077 /** 078 * Creates a skin 079 * @param id The id of the skin (e.g. the directory name) 080 * @param file The skin file 081 * @param modifiable Is this model modifiable? 082 */ 083 public SkinModel(String id, Path file, boolean modifiable) 084 { 085 _id = id; 086 _file = file; 087 _modifiable = modifiable; 088 } 089 090 /** 091 * The configuration default values (if configuration file does not exist or is unreadable) 092 */ 093 protected void _defaultValues() 094 { 095 _lastConfUpdate = new Date().getTime(); 096 097 _label = new I18nizableText(_id); 098 _description = new I18nizableText(""); 099 _smallImage = "/plugins/web/resources/img/skin/skin_16.png"; 100 _mediumImage = "/plugins/web/resources/img/skin/skin_32.png"; 101 _largeImage = "/plugins/web/resources/img/skin/skin_48.png"; 102 } 103 104 /** 105 * Is the model modifiable? 106 * @return true if modifiable 107 */ 108 public boolean isModifiable() 109 { 110 return _modifiable; 111 } 112 113 /** 114 * Refresh the conf values 115 */ 116 public void refreshValues() 117 { 118 Path configurationFile = _file.resolve("conf/skin.xml"); 119 if (Files.exists(configurationFile)) 120 { 121 try 122 { 123 long millis = Files.getLastModifiedTime(configurationFile).toMillis(); 124 if (_lastConfUpdate < millis) 125 { 126 _lastConfUpdate = millis; 127 try (InputStream is = Files.newInputStream(configurationFile)) 128 { 129 Configuration configuration = new DefaultConfigurationBuilder().build(is); 130 131 this._label = _configureI18nizableText(configuration.getChild("label", false), new I18nizableText(this._id)); 132 this._description = _configureI18nizableText(configuration.getChild("description", false), new I18nizableText("")); 133 this._smallImage = _configureThumbnail(configuration.getChild("thumbnail").getChild("small").getValue(null), "/plugins/web/resources/img/skin/skin_16.png"); 134 this._mediumImage = _configureThumbnail(configuration.getChild("thumbnail").getChild("medium").getValue(null), "/plugins/web/resources/img/skin/skin_32.png"); 135 this._largeImage = _configureThumbnail(configuration.getChild("thumbnail").getChild("large").getValue(null), "/plugins/web/resources/img/skin/skin_48.png"); 136 } 137 } 138 } 139 catch (Exception e) 140 { 141 _defaultValues(); 142 if (_logger.isWarnEnabled()) 143 { 144 _logger.warn("Cannot read the configuration file conf/skin.xml for the model '" + this._id + "'. Continue as if file was not existing", e); 145 } 146 } 147 } 148 else 149 { 150 _defaultValues(); 151 } 152 } 153 154 /** 155 * Get the default color theme 156 * @return The default color theme or <code>null</code> 157 */ 158 public String getDefaultColorTheme () 159 { 160 _refreshDefaultValues (); 161 return _defaultColorTheme; 162 } 163 164 /** 165 * Get color themes of this model 166 * @return color themes of this model 167 */ 168 public Map<String, Theme> getThemes () 169 { 170 _refreshColorsValues (); 171 return _themes; 172 } 173 174 /** 175 * Get a theme of this model 176 * @param themeId the theme id 177 * @return the theme 178 */ 179 public Theme getTheme (String themeId) 180 { 181 _refreshColorsValues(); 182 return _themes.get(themeId); 183 } 184 185 /** 186 * Get the default colors of this model 187 * @return The default colors of this model 188 */ 189 public List<String> getDefaultColors () 190 { 191 _refreshColorsValues(); 192 return _colors; 193 } 194 195 /** 196 * Get the default colors of this theme 197 * @param themeId The theme id 198 * @return the default colors of this theme 199 */ 200 public List<String> getColors (String themeId) 201 { 202 _refreshColorsValues(); 203 if (_themes.containsKey(themeId)) 204 { 205 return _themes.get(themeId).getColors(); 206 } 207 return new ArrayList<>(); 208 } 209 210 /** 211 * Get all style items of this model 212 * @return the list of items of css style 213 */ 214 public Map<String, List<CssMenuItem>> getStyleItems () 215 { 216 _refreshCssStyles(); 217 return _cssStyles; 218 } 219 220 /** 221 * Get the items of this css style 222 * @param styleId The css style id 223 * @return the list of items of css style 224 */ 225 public List<CssMenuItem> getStyleItems (String styleId) 226 { 227 _refreshCssStyles(); 228 return _cssStyles.get(styleId); 229 } 230 231 /** 232 * Get model default values 233 * @return The default values 234 */ 235 public Map<String, String> getDefaultValues () 236 { 237 _refreshDefaultValues (); 238 return _defaultValues; 239 } 240 241 private void _refreshColorsValues () 242 { 243 Path configurationFile = _file.resolve("model/colors.xml"); 244 if (Files.exists(configurationFile)) 245 { 246 try 247 { 248 long millis = Files.getLastModifiedTime(configurationFile).toMillis(); 249 if (_lastColorsUpdate < millis) 250 { 251 _lastColorsUpdate = millis; 252 _colors = new ArrayList<>(); 253 _themes = new LinkedHashMap<>(); 254 255 try (InputStream is = Files.newInputStream(configurationFile)) 256 { 257 Configuration configuration = new DefaultConfigurationBuilder().build(is); 258 Configuration[] themesConf = configuration.getChildren("theme"); 259 260 for (Configuration themeConf : themesConf) 261 { 262 String name = themeConf.getAttribute("id"); 263 I18nizableText label = _configureI18nizableText (themeConf.getChild("label"), new I18nizableText("")); 264 I18nizableText description = _configureI18nizableText (themeConf.getChild("description"), new I18nizableText("")); 265 266 Configuration[] colorsConf = themeConf.getChild("colors").getChildren("color"); 267 268 List<String> colors = new ArrayList<>(); 269 for (Configuration colorConf : colorsConf) 270 { 271 colors.add(colorConf.getValue().toLowerCase()); 272 } 273 274 _themes.put(name, new Theme(name, label, description, colors)); 275 } 276 277 Configuration[] children = configuration.getChild("default", true).getChildren("color"); 278 for (Configuration colorConf : children) 279 { 280 _colors.add(colorConf.getValue().toLowerCase()); 281 } 282 } 283 } 284 } 285 catch (Exception e) 286 { 287 if (_logger.isWarnEnabled()) 288 { 289 _logger.warn("Cannot read the configuration file model/colors.xml for the model '" + this._id + "'. Continue as if file was not existing", e); 290 } 291 292 _colors = new ArrayList<>(); 293 _themes = new HashMap<>(); 294 } 295 } 296 else 297 { 298 _colors = new ArrayList<>(); 299 _themes = new HashMap<>(); 300 } 301 } 302 303 private void _refreshCssStyles () 304 { 305 Path configurationFile = _file.resolve("model/css-styles.xml"); 306 if (Files.exists(configurationFile)) 307 { 308 try 309 { 310 long millis = Files.getLastModifiedTime(configurationFile).toMillis(); 311 if (_lastCssStylesUpdate < millis) 312 { 313 _lastCssStylesUpdate = millis; 314 _cssStyles = new HashMap<>(); 315 316 try (InputStream is = Files.newInputStream(configurationFile)) 317 { 318 Configuration configuration = new DefaultConfigurationBuilder().build(is); 319 Configuration[] children = configuration.getChildren(); 320 for (Configuration styleConf : children) 321 { 322 String name = styleConf.getName(); 323 List<CssMenuItem> items = _configureStyleItems (styleConf); 324 _cssStyles.put(name, items); 325 } 326 } 327 } 328 } 329 catch (Exception e) 330 { 331 if (_logger.isWarnEnabled()) 332 { 333 _logger.warn("Cannot read the configuration file model/css-styles.xml for the model '" + this._id + "'. Continue as if file was not existing", e); 334 } 335 336 _cssStyles = new HashMap<>(); 337 } 338 } 339 else 340 { 341 _cssStyles = new HashMap<>(); 342 } 343 } 344 345 private List<CssMenuItem> _configureStyleItems (Configuration styleConfig) throws ConfigurationException 346 { 347 List<CssMenuItem> items = new ArrayList<>(); 348 349 Configuration[] children = styleConfig.getChildren(); 350 for (Configuration child : children) 351 { 352 if (child.getName().equals("separator")) 353 { 354 items.add(new Separator()); 355 } 356 else 357 { 358 String value = child.getChild("value").getValue(); 359 I18nizableText label = _configureI18nizableText (child.getChild("label"), new I18nizableText(value)); 360 String iconCls = child.getChild("iconCls").getValue(null); 361 String icon = _configureIcon (child.getChild("icon", false)); 362 String cssClass = child.getChild("cssclass", true).getValue(null); 363 364 items.add(new CssStyleItem(value, label, iconCls, icon, cssClass)); 365 } 366 } 367 368 return items; 369 } 370 371 372 private void _refreshDefaultValues () 373 { 374 Path configurationFile = _file.resolve("model/default-values.xml"); 375 if (Files.exists(configurationFile)) 376 { 377 try 378 { 379 long millis = Files.getLastModifiedTime(configurationFile).toMillis(); 380 381 if (_lastDefaultValuesUpdate < millis) 382 { 383 _defaultValues = new HashMap<>(); 384 _lastDefaultValuesUpdate = millis; 385 386 try (InputStream is = Files.newInputStream(configurationFile)) 387 { 388 Configuration configuration = new DefaultConfigurationBuilder().build(is); 389 Configuration[] parametersConf = configuration.getChildren("parameter"); 390 391 for (Configuration paramConf : parametersConf) 392 { 393 String value = paramConf.getValue(null); 394 if (StringUtils.isNotEmpty(value)) 395 { 396 _defaultValues.put(paramConf.getAttribute("id"), paramConf.getValue()); 397 } 398 } 399 400 _defaultColorTheme = configuration.getChild("color-theme", true).getValue(null); 401 } 402 } 403 } 404 catch (Exception e) 405 { 406 _logger.error("Cannot read the configuration file model/default-values.xml for the model '" + this._id + "'. Continue as if file was not existing", e); 407 408 _defaultValues = new HashMap<>(); 409 _defaultColorTheme = null; 410 } 411 } 412 else 413 { 414 _defaultValues = new HashMap<>(); 415 _defaultColorTheme = null; 416 } 417 } 418 419 private String _configureThumbnail(String value, String defaultImage) 420 { 421 if (value == null) 422 { 423 return defaultImage; 424 } 425 else 426 { 427 return "/models/" + this._id + "/resources/" + value; 428 } 429 } 430 431 private I18nizableText _configureI18nizableText(Configuration configuration, I18nizableText defaultValue) throws ConfigurationException 432 { 433 if (configuration != null) 434 { 435 boolean i18nSupported = configuration.getAttributeAsBoolean("i18n", false); 436 if (i18nSupported) 437 { 438 String catalogue = configuration.getAttribute("catalogue", null); 439 if (catalogue == null) 440 { 441 catalogue = "model." + this._id; 442 } 443 444 return new I18nizableText(catalogue, configuration.getValue()); 445 } 446 else 447 { 448 return new I18nizableText(configuration.getValue("")); 449 } 450 } 451 else 452 { 453 return defaultValue; 454 } 455 456 } 457 458 private String _configureIcon (Configuration iconConf) throws ConfigurationException 459 { 460 if (iconConf != null) 461 { 462 String pluginName = iconConf.getAttribute("plugin"); 463 String path = iconConf.getValue(); 464 465 return "/plugins/" + pluginName + "/resources/" + path; 466 } 467 return null; 468 } 469 470 /** 471 * The skin id 472 * @return the id 473 */ 474 public String getId() 475 { 476 return _id; 477 } 478 479 /** 480 * The skin label 481 * @return The label 482 */ 483 public I18nizableText getLabel() 484 { 485 return _label; 486 } 487 /** 488 * The skin description 489 * @return The description. Can not be null but can be empty 490 */ 491 public I18nizableText getDescription() 492 { 493 return _description; 494 } 495 496 /** 497 * The small image file uri 498 * @return The small image file uri 499 */ 500 public String getSmallImage() 501 { 502 return _smallImage; 503 } 504 505 /** 506 * The medium image file uri 507 * @return The medium image file uri 508 */ 509 public String getMediumImage() 510 { 511 return _mediumImage; 512 } 513 514 /** 515 * The large image file uri 516 * @return The large image file uri 517 */ 518 public String getLargeImage() 519 { 520 return _largeImage; 521 } 522 523 /** 524 * Get the skin's file directory 525 * @return the skin's file directory 526 */ 527 public Path getPath () 528 { 529 return _file; 530 } 531 532 /*----------------------------------------------------*/ 533 /* Internal classes */ 534 /*----------------------------------------------------*/ 535 /** 536 * Bean representing a theme 537 * 538 */ 539 public class Theme 540 { 541 private String _themeId; 542 private I18nizableText _themeLabel; 543 private I18nizableText _themeDescription; 544 private List<String> _themeColors; 545 546 /** 547 * Constructor 548 * @param id the theme id 549 * @param label the theme's label 550 * @param description the theme's description 551 * @param colors the theme's colors 552 */ 553 public Theme (String id, I18nizableText label, I18nizableText description, List<String> colors) 554 { 555 _themeId = id; 556 _themeLabel = label; 557 _themeDescription = description; 558 _themeColors = colors; 559 } 560 561 /** 562 * Get the id 563 * @return the id 564 */ 565 public String getId () 566 { 567 return _themeId; 568 } 569 570 /** 571 * Get the label 572 * @return the label 573 */ 574 public I18nizableText getLabel () 575 { 576 return _themeLabel; 577 } 578 579 /** 580 * Get the description 581 * @return the description 582 */ 583 public I18nizableText getDescription () 584 { 585 return _themeDescription; 586 } 587 588 /** 589 * Get the colors 590 * @return the colors 591 */ 592 public List<String> getColors () 593 { 594 return _themeColors; 595 } 596 597 /** 598 * Get the color to the given index 599 * @param index The index of color 600 * @return The color 601 */ 602 public String getColor (int index) 603 { 604 try 605 { 606 return _themeColors.get(index); 607 } 608 catch (IndexOutOfBoundsException e) 609 { 610 // Returns the last color 611 return _themeColors.get(_themeColors.size() - 1); 612 } 613 } 614 } 615 616 /** 617 * Bean representing a item of css style 618 * 619 */ 620 public class CssStyleItem implements CssMenuItem 621 { 622 private String _cssValue; 623 private I18nizableText _styleLabel; 624 private String _styleIcon; 625 private String _styleIconCls; 626 private String _cssClass; 627 628 /** 629 * Constructor 630 * @param value the value 631 * @param label the item's label 632 * @param iconCls the CSS class for icon. Can be null. 633 * @param icon the icon. Can be null. 634 * @param cssClass the css class. Can be null. 635 */ 636 public CssStyleItem (String value, I18nizableText label, String iconCls, String icon, String cssClass) 637 { 638 _cssValue = value; 639 _styleLabel = label; 640 _styleIcon = icon; 641 _styleIconCls = iconCls; 642 _cssClass = cssClass; 643 } 644 645 /** 646 * Get the value 647 * @return the value 648 */ 649 public String getValue () 650 { 651 return _cssValue; 652 } 653 654 /** 655 * Get the label 656 * @return the label 657 */ 658 public I18nizableText getLabel () 659 { 660 return _styleLabel; 661 } 662 663 /** 664 * Get the icon css class 665 * @return the icon css class or <code>null</code> 666 */ 667 public String getIconCls () 668 { 669 return _styleIconCls; 670 } 671 672 /** 673 * Get the icon 674 * @return the icon or <code>null</code> 675 */ 676 public String getIcon () 677 { 678 return _styleIcon; 679 } 680 681 /** 682 * Get the css class 683 * @return The css class or <code>null</code> 684 */ 685 public String getCssClass () 686 { 687 return _cssClass; 688 } 689 } 690 691 /** 692 * Item representing a separator 693 */ 694 public class Separator implements CssMenuItem 695 { 696 /** 697 * Constructor 698 */ 699 public Separator() 700 { 701 // Nothing to do 702 } 703 704 } 705 706 /** 707 * Abstract representation of a menu item 708 */ 709 public interface CssMenuItem 710 { 711 // Empty 712 } 713}