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<>()); 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 values.put(id, langValues); 712 } 713 else 714 { 715 values.put(id, paramConf.getValue("")); 716 } 717 } 718 } 719 720 return values; 721 } 722 catch (Exception e) 723 { 724 getLogger().error("Unable to get values for parameters '" + StringUtils.join(ids, ", ") + "'", e); 725 return new HashMap<>(); 726 } 727 } 728 729 /** 730 * Get the current theme 731 * @param skinDir The skin directory (could be in temp, work or skin) 732 * @return The current theme id 733 */ 734 public String getColorTheme (Path skinDir) 735 { 736 Path modelFile = skinDir.resolve("model.xml"); 737 738 try (InputStream is = Files.newInputStream(modelFile)) 739 { 740 Configuration configuration = new DefaultConfigurationBuilder(true).build(is); 741 return configuration.getChild("color-theme").getValue(null); 742 } 743 catch (Exception e) 744 { 745 getLogger().error("Unable to get theme value", e); 746 return null; 747 } 748 } 749 750 /** 751 * Get all parameters' values 752 * @param skinDir The skin directory (could be in temp, work or skin) 753 * @param modelName The model name 754 * @return The all parameters' values 755 */ 756 public Map<String, Object> getParameterValues (Path skinDir, String modelName) 757 { 758 Map<String, Object> values = new HashMap<>(); 759 760 Path modelFile = skinDir.resolve("model.xml"); 761 762 try (InputStream is = Files.newInputStream(modelFile)) 763 { 764 Configuration configuration = new DefaultConfigurationBuilder(true).build(is); 765 Configuration[] parametersConf = configuration.getChild("parameters").getChildren("parameter"); 766 767 Map<String, AbstractSkinParameter> modelParameters = getModelParameters(modelName); 768 769 for (Configuration paramConf : parametersConf) 770 { 771 String id = paramConf.getAttribute("id"); 772 AbstractSkinParameter modelParam = modelParameters.get(id); 773 if (modelParam != null) 774 { 775 if (modelParam instanceof I18nizableTextParameter) 776 { 777 Configuration[] children = paramConf.getChildren(); 778 Map<String, String> langValues = new HashMap<>(); 779 for (Configuration langConfig : children) 780 { 781 langValues.put(langConfig.getName(), langConfig.getValue("")); 782 } 783 values.put(id, langValues); 784 } 785 else if (modelParam instanceof ImageParameter) 786 { 787 boolean uploaded = Boolean.valueOf(paramConf.getAttribute("uploaded", "false")); 788 values.put(id, new ImageParameter.FileValue(paramConf.getValue(""), uploaded)); 789 } 790 else 791 { 792 values.put(id, paramConf.getValue("")); 793 } 794 } 795 } 796 797 return values; 798 } 799 catch (Exception e) 800 { 801 getLogger().error("Unable to get values of all parameters", e); 802 return new HashMap<>(); 803 } 804 } 805 806 /** 807 * Modify the color theme 808 * @param skinDir The skin directory (could be in temp, work or skin) 809 * @param themeId The id of the theme. Can be null to clear theme. 810 * @return <code>true</code> is modification success 811 */ 812 public boolean saveColorTheme (Path skinDir, String themeId) 813 { 814 Path currentFile = skinDir.resolve("model.xml"); 815 816 Source currentSrc = null; 817 Source src = null; 818 try 819 { 820 currentSrc = _resolver.resolveURI("file://" + currentFile.toFile().getAbsolutePath()); 821 822 Map<String, Object> parentContext = new HashMap<>(); 823 parentContext.put("modelUri", currentSrc.getURI()); 824 if (themeId != null) 825 { 826 parentContext.put("themeId", themeId); 827 } 828 829 src = _resolver.resolveURI("cocoon://_plugins/skinfactory/change-color-theme", null, parentContext); 830 831 SourceUtil.copy(src, currentSrc); 832 833 return true; 834 } 835 catch (IOException e) 836 { 837 getLogger().error("Unable to update color theme for skin '" + skinDir.getFileName().toString() + "'", e); 838 return false; 839 } 840 finally 841 { 842 _resolver.release(src); 843 _resolver.release(currentSrc); 844 } 845 } 846 847 /** 848 * Modify a skin parameter's value 849 * @param skinDir The skin directory (could be in temp, work or skin) 850 * @param id the id of the parameter 851 * @param value the new value 852 * @return <code>true</code> is modification success 853 */ 854 public boolean saveParameter(Path skinDir, String id, Object value) 855 { 856 Map<String, Object> parameters = new HashMap<>(); 857 parameters.put(id, value); 858 859 return saveParameters (skinDir, parameters); 860 } 861 862 /** 863 * Save skin parameters 864 * @param skinDir The skin directory (could be in temp, work or skin) 865 * @param parameters The skins parameters to save 866 * @return <code>true</code> is modification success 867 */ 868 public boolean saveParameters(Path skinDir, Map<String, Object> parameters) 869 { 870 Path currentFile = skinDir.resolve("model.xml"); 871 872 Source currentSrc = null; 873 Source src = null; 874 try 875 { 876 currentSrc = _resolver.resolveURI("file://" + currentFile.toFile().getAbsolutePath()); 877 878 Map<String, Object> parentContext = new HashMap<>(); 879 parentContext.put("modelUri", currentSrc.getURI()); 880 parentContext.put("skinParameters", parameters); 881 882 src = _resolver.resolveURI("cocoon://_plugins/skinfactory/change-parameters", null, parentContext); 883 884 SourceUtil.copy(src, currentSrc); 885 886 return true; 887 } 888 catch (IOException e) 889 { 890 getLogger().error("Unable to save parameters from skin '" + skinDir.getFileName().toString() + "'", e); 891 return false; 892 } 893 finally 894 { 895 _resolver.release(src); 896 _resolver.release(currentSrc); 897 } 898 } 899 900 /** 901 * Apply model parameters in given skin 902 * @param modelName The model name 903 * @param skinDir The skin directory (could be temp, work or skins) 904 */ 905 public void applyModelParameters (String modelName, Path skinDir) 906 { 907 Map<String, Object> currentValues = getParameterValues(skinDir, modelName); 908 applyModelParameters(modelName, skinDir, currentValues); 909 } 910 911 /** 912 * Apply model parameters in given skin 913 * @param modelName The model name 914 * @param skinDir The skin directory 915 * @param values The values to set 916 */ 917 public void applyModelParameters (String modelName, Path skinDir, Map<String, Object> values) 918 { 919 Map<String, Object> parameterValues = new HashMap<>(); 920 SkinModel model = _modelsManager.getModel(modelName); 921 922 Map<String, String> modelDefaultValues = model.getDefaultValues(); 923 924 Map<String, AbstractSkinParameter> modelParameters = getModelParameters(modelName); 925 for (AbstractSkinParameter skinParameter : modelParameters.values()) 926 { 927 String paramId = skinParameter.getId(); 928 929 if (skinParameter instanceof I18nizableTextParameter) 930 { 931 Map<String, String> defaultValues = ((I18nizableTextParameter) skinParameter).getDefaultValues(); 932 @SuppressWarnings("unchecked") 933 Map<String, String> currentValues = (Map<String, String>) values.get(paramId); 934 if (currentValues == null) 935 { 936 currentValues = new HashMap<>(); 937 } 938 for (String lang : defaultValues.keySet()) 939 { 940 if (currentValues.get(lang) != null) 941 { 942 applyParameter(skinParameter, skinDir, modelName, currentValues.get(lang), lang); 943 } 944 else 945 { 946 // Default value 947 applyParameter(skinParameter, skinDir, modelName, defaultValues.get(lang), lang); 948 currentValues.put(lang, defaultValues.get(lang)); 949 } 950 } 951 parameterValues.put(skinParameter.getId(), currentValues); 952 } 953 else if (skinParameter instanceof ImageParameter) 954 { 955 FileValue imgValue = (FileValue) _getValue(model, skinParameter, values, modelDefaultValues); 956 applyParameter(skinParameter, skinDir, modelName, imgValue, null); 957 parameterValues.put(skinParameter.getId(), imgValue); 958 } 959 else 960 { 961 String value = (String) _getValue (model, skinParameter, values, modelDefaultValues); 962 applyParameter(skinParameter, skinDir, modelName, value, null); 963 parameterValues.put(skinParameter.getId(), value); 964 } 965 } 966 967 saveParameters(skinDir, parameterValues); 968 } 969 970 private Object _getValue (SkinModel model, AbstractSkinParameter param, Map<String, Object> values, Map<String, String> defaultValues) 971 { 972 String id = param.getId(); 973 974 // First search in current values 975 if (values.containsKey(id)) 976 { 977 return values.get(id); 978 } 979 980 // Then search in default values from model 981 if (defaultValues.containsKey(id)) 982 { 983 String valueAsStr = defaultValues.get(id); 984 985 if (param instanceof ImageParameter) 986 { 987 return new FileValue (valueAsStr, false); 988 } 989 else 990 { 991 return valueAsStr; 992 } 993 } 994 995 // If nor found, get the parameter default value 996 return param.getDefaultValue(model); 997 } 998 /** 999 * Update hash 1000 * @param xmlFile the xml {@link File} 1001 * @param hash the new hash of the file 1002 * @throws IOException if an error occurs while manipulating the file 1003 */ 1004 public void updateHash (Path xmlFile, String hash) throws IOException 1005 { 1006 Source currentSrc = null; 1007 Source src = null; 1008 try 1009 { 1010 currentSrc = _resolver.resolveURI("file://" + xmlFile.toFile().getAbsolutePath()); 1011 1012 Map<String, Object> parentContext = new HashMap<>(); 1013 parentContext.put("modelUri", currentSrc.getURI()); 1014 parentContext.put("hash", hash); 1015 1016 src = _resolver.resolveURI("cocoon://_plugins/skinfactory/change-hash", null, parentContext); 1017 1018 SourceUtil.copy(src, currentSrc); 1019 } 1020 finally 1021 { 1022 _resolver.release(src); 1023 _resolver.release(currentSrc); 1024 } 1025 1026 } 1027 1028 /** 1029 * Apply parameter 1030 * @param parameter the skin parameter 1031 * @param skinDir the skin directory (could be temp, work or skins) 1032 * @param modelName the model name 1033 * @param value the parameter value 1034 * @param lang The language 1035 */ 1036 public void applyParameter (AbstractSkinParameter parameter, Path skinDir, String modelName, Object value, String lang) 1037 { 1038 Path modelDir = _modelsManager.getModel(modelName).getPath(); 1039 parameter.apply(skinDir, modelDir, value, lang); 1040 } 1041 1042 /** 1043 * Apply new color theme 1044 * @param skinDir the skin directory (could be temp, work or skins) 1045 * @param modelName the model name 1046 */ 1047 public void applyColorTheme (String modelName, Path skinDir) 1048 { 1049 Path modelDir = _modelsManager.getModel(modelName).getPath(); 1050 1051 Map<String, Object> currentValues = getParameterValues(skinDir, modelName); 1052 1053 Map<String, AbstractSkinParameter> modelParameters = getModelParameters(modelName); 1054 for (AbstractSkinParameter skinParameter : modelParameters.values()) 1055 { 1056 if (skinParameter instanceof CSSColorParameter) 1057 { 1058 String value = (String) currentValues.get(skinParameter.getId()); 1059 if (StringUtils.isEmpty(value)) 1060 { 1061 value = (String) skinParameter.getDefaultValue(_modelsManager.getModel(modelName)); 1062 } 1063 1064 skinParameter.apply(skinDir, modelDir, value, null); 1065 } 1066 } 1067 } 1068 1069 private I18nizableText _configureI18nizableText(Configuration configuration, String modelName) throws ConfigurationException 1070 { 1071 boolean i18nSupported = configuration.getAttributeAsBoolean("i18n", false); 1072 1073 if (i18nSupported) 1074 { 1075 String catalogue = configuration.getAttribute("catalogue", null); 1076 if (catalogue == null) 1077 { 1078 catalogue = "model." + modelName; 1079 } 1080 1081 return new I18nizableText(catalogue, configuration.getValue()); 1082 } 1083 else 1084 { 1085 return new I18nizableText(configuration.getValue("")); 1086 } 1087 } 1088}