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