001/* 002 * Copyright 2012 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.skinfactory; 017 018import java.io.File; 019import java.io.FileFilter; 020import java.io.FileInputStream; 021import java.io.IOException; 022import java.io.InputStream; 023import java.nio.charset.StandardCharsets; 024import java.util.ArrayList; 025import java.util.HashMap; 026import java.util.LinkedHashMap; 027import java.util.List; 028import java.util.Map; 029import java.util.regex.Matcher; 030import java.util.regex.Pattern; 031 032import org.apache.avalon.framework.component.Component; 033import org.apache.avalon.framework.configuration.Configuration; 034import org.apache.avalon.framework.configuration.ConfigurationException; 035import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 036import org.apache.avalon.framework.logger.AbstractLogEnabled; 037import org.apache.avalon.framework.service.ServiceException; 038import org.apache.avalon.framework.service.ServiceManager; 039import org.apache.avalon.framework.service.Serviceable; 040import org.apache.avalon.framework.thread.ThreadSafe; 041import org.apache.commons.io.IOUtils; 042import org.apache.commons.lang.StringUtils; 043import org.apache.excalibur.source.Source; 044import org.apache.excalibur.source.SourceResolver; 045import org.apache.excalibur.source.SourceUtil; 046import org.apache.excalibur.source.impl.FileSource; 047 048import org.ametys.runtime.i18n.I18nizableText; 049import org.ametys.skinfactory.parameters.AbstractSkinParameter; 050import org.ametys.skinfactory.parameters.CSSColorParameter; 051import org.ametys.skinfactory.parameters.CSSParameter; 052import org.ametys.skinfactory.parameters.I18nizableTextParameter; 053import org.ametys.skinfactory.parameters.ImageParameter; 054import org.ametys.skinfactory.parameters.ImageParameter.FileValue; 055import org.ametys.skinfactory.parameters.TextParameter; 056import org.ametys.skinfactory.parameters.Variant; 057import org.ametys.skinfactory.parameters.VariantParameter; 058import org.ametys.web.skin.SkinModel; 059import org.ametys.web.skin.SkinModelsManager; 060 061/** 062 * Manager for skin parameters 063 * 064 */ 065public class SkinFactoryComponent extends AbstractLogEnabled implements Component, ThreadSafe, Serviceable 066{ 067 /** Avalon role */ 068 public static final String ROLE = SkinFactoryComponent.class.getName(); 069 070 /** Pattern for CSS parameters. E.g: color: #EEEEEE /* AMETYS("myuniqueid", "Label", DESCRIPTION_I18N_KEY) *\/; **/ 071 public static final Pattern CSS_PARAM_PATTERN = Pattern.compile("\\s*([^,:\\s]*)\\s*:\\s*([^:;!]*)\\s*(?:!important)?\\s*\\/\\*\\s*AMETYS\\s*\\(\\s*\"([^\"]+)\"\\s*(?:,\\s*([^,\"\\s]+|\"[^\"]*\")\\s*)?(?:,\\s*([^,\"\\s]+|\"[^\"]*\")\\s*)?\\)\\s*\\*\\/\\s*;?\\s*", Pattern.MULTILINE); 072 073 /** Pattern for i18n text parameters. E.g: <message key="SKIN_TITLE">Ametys, Web Java Open Source CMS<!-- Ametys("text.i18n.site.title", "Titre du site") --></message> */ 074 public static final Pattern I18N_PARAM_PATTERN = Pattern.compile("^\\s*<message key=\"([^,:\\s]*)\">([^<]*)<!--\\s*AMETYS\\s*\\(\\s*\"([^\"]+)\"\\s*(?:,\\s*([^,\"\\s]+|\"[^\"]*\")\\s*)?(?:,\\s*([^,\"\\s]+|\"[^\"]*\")\\s*)?\\)\\s*--></message>\\s*$", Pattern.MULTILINE); 075 076 /** Pattern for text parameters. E.g: <xsl:variable name="foo">test<!-- Ametys("variable.foo", "Foo", "Foo") --></xsl:variable> */ 077 public static final Pattern TXT_PARAM_PATTERN = Pattern.compile("^[^>]*>([^<]*)<!--\\s*AMETYS\\s*\\(\\s*\"([^\"]+)\"\\s*(?:,\\s*([^,\"\\s]+|\"[^\"]*\")\\s*)?(?:,\\s*([^,\"\\s]+|\"[^\"]*\")\\s*)?\\)\\s*-->.*$", Pattern.MULTILINE); 078 079 private static final Pattern __I18N_CATALOG_LANGUAGE = Pattern.compile("^\\s*<catalogue xml:lang=\"([a-z]{2})\">\\s*$", Pattern.MULTILINE); 080 081 private final Map<String, Map<String, AbstractSkinParameter>> _modelsParameters = new HashMap<>(); 082 private final Map<String, String> _modelsHash = new HashMap<>(); 083 084 private SkinModelsManager _modelsManager; 085 private SourceResolver _resolver; 086 087 @Override 088 public void service(ServiceManager smanager) throws ServiceException 089 { 090 _modelsManager = (SkinModelsManager) smanager.lookup(SkinModelsManager.ROLE); 091 _resolver = (SourceResolver) smanager.lookup(SourceResolver.ROLE); 092 } 093 094 /** 095 * Remove all model parameters from cache 096 */ 097 public void clearModelsParametersCache() 098 { 099 _modelsParameters.clear(); 100 } 101 102 /** 103 * Get the skin parameter of the given model 104 * @param modelName The model name 105 * @param paramId The id of the parameter 106 * @return The skin parameter or <code>null</code> if it doesn't exist 107 */ 108 public AbstractSkinParameter getModelParamater (String modelName, String paramId) 109 { 110 Map<String, AbstractSkinParameter> modelParameters = getModelParameters(modelName); 111 return modelParameters.get(paramId); 112 } 113 114 /** 115 * Determines if the model was changed 116 * @param modelName The model name 117 * @return <code>true</code> if the model was changed 118 */ 119 public boolean isModelUpToDate (String modelName) 120 { 121 String hash = _modelsManager.getModelHash(modelName); 122 if (!_modelsHash.containsKey(modelName) || !_modelsHash.get(modelName).equals(hash)) 123 { 124 // The model is not up-to-date, clear cache 125 _modelsHash.put(modelName, hash); 126 return false; 127 } 128 return true; 129 } 130 131 /** 132 * Get the skin parameters of a model 133 * @param modelName the model name 134 * @return skin parameters in a List 135 */ 136 public Map<String, AbstractSkinParameter> getModelParameters (String modelName) 137 { 138 if (!isModelUpToDate(modelName)) 139 { 140 // The model is not up-to-date, clear cache 141 _modelsParameters.remove(modelName); 142 } 143 144 if (_modelsParameters.containsKey(modelName)) 145 { 146 return _modelsParameters.get(modelName); 147 } 148 149 _modelsParameters.put(modelName, new LinkedHashMap<String, AbstractSkinParameter>()); 150 151 Map<String, AbstractSkinParameter> skinParams = _modelsParameters.get(modelName); 152 153 File modelDir = _modelsManager.getModel(modelName).getFile(); 154 155 // Variants parameters 156 skinParams.putAll(_getVariantParameters(modelDir, modelName)); 157 158 // Images Parameters 159 skinParams.putAll(_getImageParameters(modelDir, modelName)); 160 161 // CSS parameters 162 skinParams.putAll(_getCSSParameters(modelDir, modelName)); 163 164 // I18n parameters 165 skinParams.putAll(_getI18nTextParameters(modelDir, modelName)); 166 167 // Text parameters 168 skinParams.putAll(_getTextParameters(modelDir, modelName)); 169 170 return skinParams; 171 } 172 173 private Map<String, VariantParameter> _getVariantParameters (File modelDir, String modelName) 174 { 175 Map<String, VariantParameter> params = new HashMap<>(); 176 177 File variantsDir = new File(modelDir, "model/variants"); 178 if (variantsDir.exists()) 179 { 180 _findVariantsParameters (variantsDir, params, modelName); 181 } 182 return params; 183 } 184 185 private void _findVariantsParameters (File variantsDir, Map<String, VariantParameter> params, String modelName) 186 { 187 File[] listFiles = variantsDir.listFiles(new SkinDirectoryFilter()); 188 for (File child : listFiles) 189 { 190 String filename = child.getName(); 191 List<Variant> values = _getVariantValues (child, modelName); 192 193 // avoid false directory such as CVS 194 if (values.size() != 0) 195 { 196 VariantParameter variantParameter = new VariantParameter(filename, new I18nizableText(filename), new I18nizableText(""), values); 197 _configureVariant (child, variantParameter, modelName); 198 params.put(variantParameter.getId(), variantParameter); 199 } 200 else 201 { 202 getLogger().debug("Discarding variant " + child.getAbsolutePath() + " because it has no sub directories as values"); 203 } 204 } 205 } 206 207 private List<Variant> _getVariantValues (File variant, String modelName) 208 { 209 List<Variant> values = new ArrayList<>(); 210 211 for (File child : variant.listFiles(new SkinDirectoryFilter())) 212 { 213 String id = child.getName(); 214 215 // Thumbnail 216 String thumbnailPath = null; 217 File thumbnailFile = new File(child, id + ".png"); 218 if (thumbnailFile.exists()) 219 { 220 thumbnailPath = thumbnailFile.getAbsolutePath().substring(variant.getParentFile().getAbsolutePath().length() + 1).replaceAll("\\\\", "/"); 221 } 222 223 I18nizableText label = new I18nizableText(id); 224 I18nizableText description = new I18nizableText(""); 225 File file = new File(child, id + ".xml"); 226 if (file.exists()) 227 { 228 try (InputStream is = new FileInputStream(file)) 229 { 230 Configuration configuration = new DefaultConfigurationBuilder().build(is); 231 label = _configureI18nizableText(configuration.getChild("label"), modelName); 232 233 description = _configureI18nizableText(configuration.getChild("description"), modelName); 234 } 235 catch (Exception e) 236 { 237 getLogger().error("Unable to configure variant '" + id + "'", e); 238 } 239 } 240 241 values.add(new Variant(id, label, description, thumbnailPath)); 242 } 243 return values; 244 } 245 246 private void _configureVariant (File variantFile, VariantParameter param, String modelName) 247 { 248 File file = new File(variantFile + "/" + variantFile.getName() + ".xml"); 249 250 if (file.exists()) 251 { 252 try (InputStream is = new FileInputStream(file)) 253 { 254 Configuration configuration = new DefaultConfigurationBuilder().build(is); 255 256 I18nizableText label = _configureI18nizableText(configuration.getChild("label"), modelName); 257 param.setLabel(label); 258 259 I18nizableText description = _configureI18nizableText(configuration.getChild("description"), modelName); 260 param.setDescription(description); 261 262 param.setIconGlyph(configuration.getChild("icon-glyph").getValue(null)); 263 } 264 catch (Exception e) 265 { 266 getLogger().error("Unable to configure variant '" + param.getId() + "'", e); 267 } 268 } 269 270 File iconSmall = new File(variantFile + "/thumbnail_16.png"); 271 if (iconSmall.exists()) 272 { 273 param.setIconSmall(iconSmall.getName()); 274 } 275 276 File iconLarge = new File(variantFile + "/thumbnail_32.png"); 277 if (iconLarge.exists()) 278 { 279 param.setIconLarge(iconLarge.getName()); 280 } 281 } 282 283 private Map<String, ImageParameter> _getImageParameters (File modelDir, String modelName) 284 { 285 Map<String, ImageParameter> params = new HashMap<>(); 286 287 File imagesDir = new File(modelDir, "model/images"); 288 if (imagesDir.exists()) 289 { 290 _findImagesParameters (modelDir, imagesDir, imagesDir, params, modelName); 291 } 292 return params; 293 } 294 295 private void _findImagesParameters (File modelDir, File imagesDir, File file, Map<String, ImageParameter> params, String modelName) 296 { 297 File[] listFiles = file.listFiles(); 298 for (File child : listFiles) 299 { 300 if (child.isDirectory()) 301 { 302 String filename = child.getName(); 303 String lcFilename = filename.toLowerCase(); 304 if (lcFilename.endsWith(".png") || lcFilename.endsWith(".jpg") || lcFilename.endsWith(".jpeg") || lcFilename.endsWith(".gif")) 305 { 306 String imagePath = child.getAbsolutePath().substring(imagesDir.getAbsolutePath().length() + 1); 307 ImageParameter imageParameter = new ImageParameter(imagePath, new I18nizableText(filename), new I18nizableText("")); 308 _configureImage (child, imageParameter, modelName); 309 params.put(imageParameter.getId(), imageParameter); 310 } 311 else 312 { 313 _findImagesParameters (modelDir, imagesDir, child, params, modelName); 314 } 315 } 316 } 317 } 318 319 private void _configureImage (File imageFile, ImageParameter param, String modelName) 320 { 321 String filename = imageFile.getName(); 322 int i = filename.lastIndexOf("."); 323 if (i > 0) 324 { 325 filename = filename.substring(0, i); 326 } 327 File file = new File(imageFile + "/" + filename + ".xml"); 328 329 if (file.exists()) 330 { 331 try (InputStream is = new FileInputStream(file)) 332 { 333 Configuration configuration = new DefaultConfigurationBuilder().build(is); 334 335 I18nizableText label = _configureI18nizableText(configuration.getChild("label"), modelName); 336 param.setLabel(label); 337 338 I18nizableText description = _configureI18nizableText(configuration.getChild("description"), modelName); 339 param.setDescription(description); 340 341 param.setIconGlyph(configuration.getChild("icon-glyph").getValue(null)); 342 } 343 catch (Exception e) 344 { 345 getLogger().error("Unable to configure image parameter '" + param.getId() + "'", e); 346 } 347 } 348 349 File iconSmall = new File(imageFile + "/thumbnail_16.png"); 350 if (iconSmall.exists()) 351 { 352 param.setIconSmall(iconSmall.getName()); 353 } 354 File iconLarge = new File(imageFile + "/thumbnail_32.png"); 355 if (iconLarge.exists()) 356 { 357 param.setIconLarge(iconLarge.getName()); 358 } 359 } 360 361 362 private Map<String, CSSParameter> _getCSSParameters(File modelDir, String modelName) 363 { 364 Map<String, CSSParameter> cssParams = new HashMap<>(); 365 366 // Parse css files in the root resources directory. 367 for (File file : _listCSSFiles (new File(modelDir, "/resources"))) 368 { 369 _parseCSSFile(cssParams, modelName, file); 370 } 371 372 // Parse css files for each templates. 373 File templatesDir = new File(modelDir, "/templates"); 374 if (templatesDir.isDirectory()) 375 { 376 File[] templates = templatesDir.listFiles(); 377 if (templates != null) 378 { 379 for (File template : templates) 380 { 381 for (File file : _listCSSFiles (new File(template, "resources"))) 382 { 383 _parseCSSFile(cssParams, modelName, file); 384 } 385 } 386 } 387 } 388 389 // Parse xsl files for inline css 390 for (File file : _listXSLFiles(modelDir)) 391 { 392 if (file.isFile()) 393 { 394 _parseCSSFile(cssParams, modelName, file); 395 } 396 } 397 398 return cssParams; 399 } 400 401 private List<File> _listCSSFiles (File file) 402 { 403 List<File> files = new ArrayList<>(); 404 405 File[] listFiles = file.listFiles(); 406 if (listFiles != null) 407 { 408 for (File child : listFiles) 409 { 410 if (child.isFile() && child.getName().endsWith(".css")) 411 { 412 files.add(child); 413 } 414 else if (child.isDirectory()) 415 { 416 files.addAll(_listCSSFiles(child)); 417 } 418 } 419 } 420 421 return files; 422 } 423 424 private void _parseCSSFile (Map<String, CSSParameter> cssParams, String modelName, File cssFile) 425 { 426 try (InputStream is = new FileInputStream(cssFile)) 427 { 428 String string = IOUtils.toString(is, "UTF-8"); 429 430 Matcher m = CSS_PARAM_PATTERN.matcher(string); 431 while (m.find()) 432 { 433 String id = m.group(3); 434 if (cssParams.containsKey(id)) 435 { 436 CSSParameter cssParameter = cssParams.get(id); 437 cssParameter.addCSSFile(cssFile); 438 439 I18nizableText label = m.group(4) != null ? _parseI18nizableText(m.group(4), modelName) : null; 440 if (label != null) 441 { 442 cssParameter.setLabel(label); 443 } 444 445 I18nizableText description = m.group(5) != null ? _parseI18nizableText(m.group(5), modelName) : null; 446 if (description != null) 447 { 448 cssParameter.setDescription(description); 449 } 450 } 451 else 452 { 453 String cssProperty = m.group(1); 454 String defaultValue = m.group(2).trim(); 455 456 I18nizableText label = m.group(4) != null ? _parseI18nizableText(m.group(4), modelName) : null; 457 I18nizableText description = m.group(5) != null ? _parseI18nizableText(m.group(5), modelName) : null; 458 459 if (cssProperty.equals("color") || cssProperty.equals("background-color") || cssProperty.equals("border-color")) 460 { 461 CSSColorParameter cssParameter = new CSSColorParameter (id, label, description, cssFile, cssProperty, defaultValue, _modelsManager.getModel(modelName), this); 462 cssParams.put(id, cssParameter); 463 } 464 else 465 { 466 CSSParameter cssParameter = new CSSParameter(id, label, description, cssFile, cssProperty, defaultValue); 467 cssParams.put(id, cssParameter); 468 } 469 } 470 } 471 } 472 catch (IOException e) 473 { 474 getLogger().error("Unable to parse file '" + cssFile.getName() + "'", e); 475 } 476 } 477 478 private Map<String, TextParameter> _getTextParameters(File modelDir, String modelName) 479 { 480 Map<String, TextParameter> textParams = new HashMap<>(); 481 482 for (File file : _listXSLFiles(modelDir)) 483 { 484 if (file.isFile()) 485 { 486 textParams.putAll(_parseXSLFile(modelName, file)); 487 } 488 } 489 490 return textParams; 491 } 492 493 private List<File> _listXSLFiles (File file) 494 { 495 List<File> files = new ArrayList<>(); 496 497 File[] listFiles = file.listFiles(); 498 if (listFiles != null) 499 { 500 for (File child : listFiles) 501 { 502 if (child.isFile() && child.getName().endsWith(".xsl")) 503 { 504 files.add(child); 505 } 506 else if (child.isDirectory()) 507 { 508 files.addAll(_listXSLFiles(child)); 509 } 510 } 511 } 512 513 return files; 514 } 515 516 private Map<String, TextParameter> _parseXSLFile (String skinName, File xslFile) 517 { 518 Map<String, TextParameter> txtParams = new LinkedHashMap<>(); 519 520 try (InputStream is = new FileInputStream(xslFile)) 521 { 522 String string = IOUtils.toString(is, "UTF-8"); 523 524 Matcher m = TXT_PARAM_PATTERN.matcher(string); 525 while (m.find()) 526 { 527 String id = m.group(2); 528 String defaultValue = m.group(1); 529 530 I18nizableText label = m.group(3) != null ? _parseI18nizableText(m.group(3), skinName) : new I18nizableText(id); 531 I18nizableText description = m.group(4) != null ? _parseI18nizableText(m.group(4), skinName) : new I18nizableText(""); 532 533 TextParameter txtParam = new TextParameter(id, label, description, xslFile, defaultValue); 534 txtParams.put(id, txtParam); 535 } 536 } 537 catch (IOException e) 538 { 539 getLogger().error("Unable to parse file '" + xslFile.getName() + "'", e); 540 } 541 542 return txtParams; 543 } 544 545 private Map<String, I18nizableTextParameter> _getI18nTextParameters(File modelDir, String modelName) 546 { 547 Map<String, I18nizableTextParameter> i18nParams = new HashMap<>(); 548 549 File i18nDir = new File(modelDir, "/i18n"); 550 for (File file : i18nDir.listFiles()) 551 { 552 if (file.getName().equals("messages.xml")) 553 { 554 i18nParams.putAll(_parseI18nFile(modelName, file)); 555 } 556 } 557 558 return i18nParams; 559 } 560 561 562 private Map<String, I18nizableTextParameter> _parseI18nFile (String skinName, File i18nFile) 563 { 564 Map<String, I18nizableTextParameter> i18nParams = new LinkedHashMap<>(); 565 566 try (InputStream is = new FileInputStream(i18nFile)) 567 { 568 String string = IOUtils.toString(is, StandardCharsets.UTF_8); 569 570 String defaultLang = _getCatalogLanguage (string, i18nFile.getName()); 571 572 Matcher m = I18N_PARAM_PATTERN.matcher(string); 573 while (m.find()) 574 { 575 String i18nKey = m.group(1); 576 String defaultValue = m.group(2).trim(); 577 String id = m.group(3); 578 I18nizableText label = m.group(4) != null ? _parseI18nizableText(m.group(4), skinName) : new I18nizableText(id); 579 I18nizableText description = m.group(5) != null ? _parseI18nizableText(m.group(5), skinName) : new I18nizableText(""); 580 581 // Default values 582 Map<String, String> defaultValues = new HashMap<>(); 583 defaultValues.put(defaultLang, defaultValue); 584 defaultValues.putAll(_getI18nOtherDefaultValues (i18nFile, i18nKey)); 585 586 I18nizableTextParameter i18nParam = new I18nizableTextParameter(id, label, description, i18nKey, defaultValues); 587 i18nParams.put(id, i18nParam); 588 } 589 } 590 catch (IOException e) 591 { 592 getLogger().error("Unable to parse file '" + i18nFile.getName() + "'", e); 593 } 594 595 return i18nParams; 596 } 597 598 private String _getCatalogLanguage (String string, String fileName) 599 { 600 Matcher m = __I18N_CATALOG_LANGUAGE.matcher(string); 601 if (m.find()) 602 { 603 return m.group(1); 604 } 605 else if (fileName.startsWith("messages_")) 606 { 607 return fileName.substring("messages_".length(), 2); 608 } 609 return "en"; 610 } 611 612 private Map<String, String> _getI18nOtherDefaultValues (File defaultCatalog, String i18nKey) 613 { 614 Pattern pattern = Pattern.compile("^\\s*<message key=\"" + i18nKey + "\">([^<]*)</message>\\s*$", Pattern.MULTILINE); 615 616 Map<String, String> defaultValues = new HashMap<>(); 617 618 for (File file : defaultCatalog.getParentFile().listFiles()) 619 { 620 String filename = file.getName(); 621 if (filename.startsWith("messages") && !filename.equals(defaultCatalog.getName())) 622 { 623 try (InputStream is = new FileInputStream(file)) 624 { 625 String string = org.apache.commons.io.IOUtils.toString(is, "UTF-8"); 626 627 String lang = _getCatalogLanguage (string, filename); 628 629 Matcher m = pattern.matcher(string); 630 if (m.find()) 631 { 632 String value = m.group(1); 633 defaultValues.put(lang, value); 634 } 635 } 636 catch (IOException e) 637 { 638 getLogger().error("Unable to parse file '" + file.getName() + "'", e); 639 } 640 } 641 } 642 643 return defaultValues; 644 } 645 646 private I18nizableText _parseI18nizableText (String label, String modelName) 647 { 648 if (label.startsWith("\"") && label.endsWith("\"")) 649 { 650 return new I18nizableText(label.substring(1, label.length() - 1)); 651 } 652 else 653 { 654 return new I18nizableText("model." + modelName, label); 655 } 656 } 657 658 /** 659 * Get the parameter's value 660 * @param skinDir The skin directory (could be in temp, work or skin) 661 * @param modelName The model name 662 * @param id The parameter id 663 * @return The parameter's value 664 */ 665 public Object getParameterValue (File skinDir, String modelName, String id) 666 { 667 List<String> ids = new ArrayList<>(); 668 ids.add(id); 669 670 return getParameterValues (skinDir, modelName, ids).get(id); 671 } 672 673 /** 674 * Get the parameters' values 675 * @param skinDir The skin directory (could be in temp, work or skin) 676 * @param modelName The model name 677 * @param ids The parameters' id 678 * @return The parameters' values 679 */ 680 public Map<String, Object> getParameterValues (File skinDir, String modelName, List<String> ids) 681 { 682 Map<String, Object> values = new HashMap<>(); 683 684 File modelFile = new File(skinDir, "model.xml"); 685 686 Source src = null; 687 try 688 { 689 src = new FileSource("file", modelFile); 690 691 Configuration configuration = new DefaultConfigurationBuilder(true).build(src.getInputStream()); 692 Configuration[] parametersConf = configuration.getChild("parameters").getChildren("parameter"); 693 694 for (Configuration paramConf : parametersConf) 695 { 696 String id = paramConf.getAttribute("id"); 697 if (ids.contains(id)) 698 { 699 AbstractSkinParameter modelParam = getModelParamater(modelName, id); 700 if (modelParam instanceof I18nizableTextParameter) 701 { 702 Configuration[] children = paramConf.getChildren(); 703 Map<String, String> langValues = new HashMap<>(); 704 for (Configuration langConfig : children) 705 { 706 langValues.put(langConfig.getName(), langConfig.getValue("")); 707 } 708 } 709 else 710 { 711 values.put(id, paramConf.getValue("")); 712 } 713 } 714 } 715 716 return values; 717 } 718 catch (Exception e) 719 { 720 getLogger().error("Unable to get values for parameters '" + StringUtils.join(ids, ", ") + "'", e); 721 return new HashMap<>(); 722 } 723 finally 724 { 725 if (src != null) 726 { 727 _resolver.release(src); 728 } 729 } 730 } 731 732 /** 733 * Get the current theme 734 * @param skinDir The skin directory (could be in temp, work or skin) 735 * @return The current theme id 736 */ 737 public String getColorTheme (File skinDir) 738 { 739 File modelFile = new File(skinDir, "model.xml"); 740 741 Source src = null; 742 try 743 { 744 src = new FileSource("file", modelFile); 745 746 Configuration configuration = new DefaultConfigurationBuilder(true).build(src.getInputStream()); 747 return configuration.getChild("color-theme").getValue(null); 748 } 749 catch (Exception e) 750 { 751 getLogger().error("Unable to get theme value", e); 752 return null; 753 } 754 finally 755 { 756 if (src != null) 757 { 758 _resolver.release(src); 759 } 760 } 761 } 762 763 /** 764 * Get all parameters' values 765 * @param skinDir The skin directory (could be in temp, work or skin) 766 * @param modelName The model name 767 * @return The all parameters' values 768 */ 769 public Map<String, Object> getParameterValues (File skinDir, String modelName) 770 { 771 Map<String, Object> values = new HashMap<>(); 772 773 File modelFile = new File(skinDir, "model.xml"); 774 775 Source src = null; 776 try 777 { 778 779 src = new FileSource("file", modelFile); 780 781 Configuration configuration = new DefaultConfigurationBuilder(true).build(src.getInputStream()); 782 Configuration[] parametersConf = configuration.getChild("parameters").getChildren("parameter"); 783 784 Map<String, AbstractSkinParameter> modelParameters = getModelParameters(modelName); 785 786 for (Configuration paramConf : parametersConf) 787 { 788 String id = paramConf.getAttribute("id"); 789 AbstractSkinParameter modelParam = modelParameters.get(id); 790 if (modelParam != null) 791 { 792 if (modelParam instanceof I18nizableTextParameter) 793 { 794 Configuration[] children = paramConf.getChildren(); 795 Map<String, String> langValues = new HashMap<>(); 796 for (Configuration langConfig : children) 797 { 798 langValues.put(langConfig.getName(), langConfig.getValue("")); 799 } 800 values.put(id, langValues); 801 } 802 else if (modelParam instanceof ImageParameter) 803 { 804 boolean uploaded = Boolean.valueOf(paramConf.getAttribute("uploaded", "false")); 805 values.put(id, new ImageParameter.FileValue(paramConf.getValue(""), uploaded)); 806 } 807 else 808 { 809 values.put(id, paramConf.getValue("")); 810 } 811 } 812 } 813 814 return values; 815 } 816 catch (Exception e) 817 { 818 getLogger().error("Unable to get values of all parameters", e); 819 return new HashMap<>(); 820 } 821 finally 822 { 823 if (src != null) 824 { 825 _resolver.release(src); 826 } 827 } 828 } 829 830 /** 831 * Modify the color theme 832 * @param skinDir The skin directory (could be in temp, work or skin) 833 * @param themeId The id of the theme. Can be null to clear theme. 834 * @return <code>true</code> is modification success 835 */ 836 public boolean saveColorTheme (File skinDir, String themeId) 837 { 838 File currentFile = new File(skinDir, "model.xml"); 839 840 Source currentSrc = null; 841 Source src = null; 842 try 843 { 844 currentSrc = _resolver.resolveURI("file://" + currentFile.getAbsolutePath()); 845 846 Map<String, Object> parentContext = new HashMap<>(); 847 parentContext.put("modelUri", currentSrc.getURI()); 848 if (themeId != null) 849 { 850 parentContext.put("themeId", themeId); 851 } 852 853 src = _resolver.resolveURI("cocoon://_plugins/skinfactory/change-color-theme", null, parentContext); 854 855 SourceUtil.copy(src, currentSrc); 856 857 return true; 858 } 859 catch (IOException e) 860 { 861 getLogger().error("Unable to update color theme for skin '" + skinDir.getName() + "'", e); 862 return false; 863 } 864 finally 865 { 866 _resolver.release(src); 867 _resolver.release(currentSrc); 868 } 869 } 870 871 /** 872 * Modify a skin parameter's value 873 * @param skinDir The skin directory (could be in temp, work or skin) 874 * @param id the id of the parameter 875 * @param value the new value 876 * @return <code>true</code> is modification success 877 */ 878 public boolean saveParameter(File skinDir, String id, Object value) 879 { 880 Map<String, Object> parameters = new HashMap<>(); 881 parameters.put(id, value); 882 883 return saveParameters (skinDir, parameters); 884 } 885 886 /** 887 * Save skin parameters 888 * @param skinDir The skin directory (could be in temp, work or skin) 889 * @param parameters The skins parameters to save 890 * @return <code>true</code> is modification success 891 */ 892 public boolean saveParameters(File skinDir, Map<String, Object> parameters) 893 { 894 File currentFile = new File(skinDir, "model.xml"); 895 896 Source currentSrc = null; 897 Source src = null; 898 try 899 { 900 currentSrc = _resolver.resolveURI("file://" + currentFile.getAbsolutePath()); 901 902 Map<String, Object> parentContext = new HashMap<>(); 903 parentContext.put("modelUri", currentSrc.getURI()); 904 parentContext.put("skinParameters", parameters); 905 906 src = _resolver.resolveURI("cocoon://_plugins/skinfactory/change-parameters", null, parentContext); 907 908 SourceUtil.copy(src, currentSrc); 909 910 return true; 911 } 912 catch (IOException e) 913 { 914 getLogger().error("Unable to save parameters from skin '" + skinDir.getName() + "'", e); 915 return false; 916 } 917 finally 918 { 919 _resolver.release(src); 920 _resolver.release(currentSrc); 921 } 922 } 923 924 /** 925 * Apply model parameters in given skin 926 * @param modelName The model name 927 * @param skinDir The skin directory (could be temp, work or skins) 928 */ 929 public void applyModelParameters (String modelName, File skinDir) 930 { 931 Map<String, Object> currentValues = getParameterValues(skinDir, modelName); 932 applyModelParameters (modelName, skinDir, currentValues); 933 } 934 935 /** 936 * Apply model parameters in given skin 937 * @param modelName The model name 938 * @param skinDir The skin directory 939 * @param values The values to set 940 */ 941 public void applyModelParameters (String modelName, File skinDir, Map<String, Object> values) 942 { 943 Map<String, Object> parameterValues = new HashMap<>(); 944 SkinModel model = _modelsManager.getModel(modelName); 945 946 Map<String, String> modelDefaultValues = model.getDefaultValues(); 947 948 Map<String, AbstractSkinParameter> modelParameters = getModelParameters(modelName); 949 for (AbstractSkinParameter skinParameter : modelParameters.values()) 950 { 951 String paramId = skinParameter.getId(); 952 953 if (skinParameter instanceof I18nizableTextParameter) 954 { 955 Map<String, String> defaultValues = ((I18nizableTextParameter) skinParameter).getDefaultValues(); 956 @SuppressWarnings("unchecked") 957 Map<String, String> currentValues = (Map<String, String>) values.get(paramId); 958 if (currentValues == null) 959 { 960 currentValues = new HashMap<>(); 961 } 962 for (String lang : defaultValues.keySet()) 963 { 964 if (currentValues.get(lang) != null) 965 { 966 applyParameter(skinParameter, skinDir, modelName, currentValues.get(lang), lang); 967 } 968 else 969 { 970 // Default value 971 applyParameter(skinParameter, skinDir, modelName, defaultValues.get(lang), lang); 972 currentValues.put(lang, defaultValues.get(lang)); 973 } 974 } 975 parameterValues.put(skinParameter.getId(), currentValues); 976 } 977 else if (skinParameter instanceof ImageParameter) 978 { 979 FileValue imgValue = (FileValue) _getValue(model, skinParameter, values, modelDefaultValues); 980 applyParameter(skinParameter, skinDir, modelName, imgValue, null); 981 parameterValues.put(skinParameter.getId(), imgValue); 982 } 983 else 984 { 985 String value = (String) _getValue (model, skinParameter, values, modelDefaultValues); 986 applyParameter(skinParameter, skinDir, modelName, value, null); 987 parameterValues.put(skinParameter.getId(), value); 988 } 989 } 990 991 saveParameters(skinDir, parameterValues); 992 } 993 994 private Object _getValue (SkinModel model, AbstractSkinParameter param, Map<String, Object> values, Map<String, String> defaultValues) 995 { 996 String id = param.getId(); 997 998 // First search in current values 999 if (values.containsKey(id)) 1000 { 1001 return values.get(id); 1002 } 1003 1004 // Then search in default values from model 1005 if (defaultValues.containsKey(id)) 1006 { 1007 String valueAsStr = defaultValues.get(id); 1008 1009 if (param instanceof ImageParameter) 1010 { 1011 return new FileValue (valueAsStr, false); 1012 } 1013 else 1014 { 1015 return valueAsStr; 1016 } 1017 } 1018 1019 // If nor found, get the parameter default value 1020 return param.getDefaultValue(model); 1021 } 1022 /** 1023 * Update hash 1024 * @param xmlFile the xml {@link File} 1025 * @param hash the new hash of the file 1026 * @throws IOException if an error occurs while manipulating the file 1027 */ 1028 public void updateHash (File xmlFile, String hash) throws IOException 1029 { 1030 Source currentSrc = null; 1031 Source src = null; 1032 try 1033 { 1034 currentSrc = _resolver.resolveURI("file://" + xmlFile.getAbsolutePath()); 1035 1036 Map<String, Object> parentContext = new HashMap<>(); 1037 parentContext.put("modelUri", currentSrc.getURI()); 1038 parentContext.put("hash", hash); 1039 1040 src = _resolver.resolveURI("cocoon://_plugins/skinfactory/change-hash", null, parentContext); 1041 1042 SourceUtil.copy(src, currentSrc); 1043 } 1044 finally 1045 { 1046 _resolver.release(src); 1047 _resolver.release(currentSrc); 1048 } 1049 1050 } 1051 1052 /** 1053 * Apply parameter 1054 * @param parameter the skin parameter 1055 * @param skinDir the skin directory (could be temp, work or skins) 1056 * @param modelName the model name 1057 * @param value the parameter value 1058 * @param lang The language 1059 */ 1060 public void applyParameter (AbstractSkinParameter parameter, File skinDir, String modelName, Object value, String lang) 1061 { 1062 File modelDir = _modelsManager.getModel(modelName).getFile(); 1063 parameter.apply(skinDir, modelDir, value, lang); 1064 } 1065 1066 /** 1067 * Apply new color theme 1068 * @param skinDir the skin directory (could be temp, work or skins) 1069 * @param modelName the model name 1070 */ 1071 public void applyColorTheme (String modelName, File skinDir) 1072 { 1073 File modelDir = _modelsManager.getModel(modelName).getFile(); 1074 1075 Map<String, Object> currentValues = getParameterValues(skinDir, modelName); 1076 1077 Map<String, AbstractSkinParameter> modelParameters = getModelParameters(modelName); 1078 for (AbstractSkinParameter skinParameter : modelParameters.values()) 1079 { 1080 if (skinParameter instanceof CSSColorParameter) 1081 { 1082 String value = (String) currentValues.get(skinParameter.getId()); 1083 if (StringUtils.isEmpty(value)) 1084 { 1085 value = (String) skinParameter.getDefaultValue(_modelsManager.getModel(modelName)); 1086 } 1087 1088 skinParameter.apply(skinDir, modelDir, value, null); 1089 } 1090 } 1091 } 1092 1093 private I18nizableText _configureI18nizableText(Configuration configuration, String modelName) throws ConfigurationException 1094 { 1095 boolean i18nSupported = configuration.getAttributeAsBoolean("i18n", false); 1096 1097 if (i18nSupported) 1098 { 1099 String catalogue = configuration.getAttribute("catalogue", null); 1100 if (catalogue == null) 1101 { 1102 catalogue = "model." + modelName; 1103 } 1104 1105 return new I18nizableText(catalogue, configuration.getValue()); 1106 } 1107 else 1108 { 1109 return new I18nizableText(configuration.getValue("")); 1110 } 1111 } 1112 1113 class SkinDirectoryFilter implements FileFilter 1114 { 1115 @Override 1116 public boolean accept(File file) 1117 { 1118 String filename = file.getName(); 1119 return file.isDirectory() && !filename.equals("CVS") && !filename.equals(".svn"); 1120 } 1121 } 1122}