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