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