001/* 002 * Copyright 2015 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.web.skin.actions; 017 018import java.io.File; 019import java.io.FileInputStream; 020import java.io.IOException; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.Comparator; 024import java.util.HashMap; 025import java.util.LinkedHashMap; 026import java.util.List; 027import java.util.Map; 028import java.util.Map.Entry; 029import java.util.TreeMap; 030 031import org.apache.avalon.framework.configuration.Configuration; 032import org.apache.avalon.framework.configuration.ConfigurationException; 033import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 034import org.apache.avalon.framework.context.Context; 035import org.apache.avalon.framework.context.ContextException; 036import org.apache.avalon.framework.context.Contextualizable; 037import org.apache.avalon.framework.parameters.Parameters; 038import org.apache.avalon.framework.service.ServiceException; 039import org.apache.avalon.framework.service.ServiceManager; 040import org.apache.cocoon.ProcessingException; 041import org.apache.cocoon.acting.ServiceableAction; 042import org.apache.cocoon.environment.ObjectModelHelper; 043import org.apache.cocoon.environment.Redirector; 044import org.apache.cocoon.environment.Request; 045import org.apache.cocoon.environment.SourceResolver; 046import org.apache.commons.io.FileUtils; 047import org.apache.commons.lang3.StringUtils; 048import org.apache.excalibur.xml.sax.SAXParser; 049import org.slf4j.LoggerFactory; 050import org.xml.sax.Attributes; 051import org.xml.sax.InputSource; 052import org.xml.sax.SAXException; 053import org.xml.sax.helpers.DefaultHandler; 054 055import org.ametys.core.cocoon.JSonReader; 056import org.ametys.runtime.i18n.I18nizableText; 057import org.ametys.runtime.parameter.AbstractParameterParser; 058import org.ametys.runtime.parameter.Enumerator; 059import org.ametys.runtime.parameter.ParameterHelper; 060import org.ametys.runtime.parameter.ParameterHelper.ParameterType; 061import org.ametys.runtime.parameter.Validator; 062import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 063import org.ametys.runtime.util.AmetysHomeHelper; 064import org.ametys.web.skin.Skin; 065import org.ametys.web.skin.SkinParameter; 066import org.ametys.web.skin.SkinsManager; 067 068/** 069 * Get the skin configuration parameters and their values 070 */ 071public class SkinConfigurationAction extends ServiceableAction implements Contextualizable 072{ 073 private SkinsManager _skinsManager; 074 private SAXParser _saxParser; 075 private Context _context; 076 077 @Override 078 public void contextualize(Context context) throws ContextException 079 { 080 _context = context; 081 } 082 083 @Override 084 public void service(ServiceManager serviceManager) throws ServiceException 085 { 086 super.service(serviceManager); 087 _skinsManager = (SkinsManager) serviceManager.lookup(SkinsManager.ROLE); 088 _saxParser = (SAXParser) serviceManager.lookup(SAXParser.ROLE); 089 } 090 091 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 092 { 093 Request request = ObjectModelHelper.getRequest(objectModel); 094 095 String skinName = request.getParameter("skinName"); 096 String tempDir = request.getParameter("tempDir"); 097 String skinDirName = request.getParameter("skinDir"); 098 099 File skinDir = null; 100 if (StringUtils.isEmpty(tempDir)) 101 { 102 Skin skin = _skinsManager.getSkin(skinName); 103 skinDir = new File(skin.getLocation()); 104 } 105 else 106 { 107 File ametysTmpDir = AmetysHomeHelper.getAmetysHomeTmp(); 108 skinDir = FileUtils.getFile(ametysTmpDir, tempDir.replace('/', File.separatorChar), skinDirName); 109 } 110 111 Map<String, Object> result = new HashMap<>(); 112 result.put("parameters", _parameters2json(skinDir)); 113 result.put("values", _values2json(skinDir)); 114 115 request.setAttribute(JSonReader.OBJECT_TO_READ, result); 116 return EMPTY_MAP; 117 } 118 119 private Map<String, Object> _values2json (File skinDir) 120 { 121 Map<String, Object> form = new HashMap<>(); 122 Map<String, Object> values = new HashMap<>(); 123 124 File configFile = new File(skinDir, "stylesheets" + File.separator + "config" + File.separator + "config.xsl"); 125 if (configFile.exists()) 126 { 127 try (FileInputStream is = new FileInputStream(new File(skinDir, "stylesheets" + File.separator + "config" + File.separator + "config.xsl"))) 128 { 129 SkinConfigHandler handler = new SkinConfigHandler(); 130 _saxParser.parse(new InputSource(is), handler); 131 132 values.putAll(handler.getVariables()); 133 } 134 catch (SAXException | IOException e) 135 { 136 getLogger().error("Failed to read skin's parameters values.", e); 137 } 138 } 139 140 form.put("values", values); 141 return form; 142 } 143 144 private Map<String, Object> _parameters2json (File skinDir) throws ProcessingException 145 { 146 Map<String, Object> parameters = new HashMap<>(); 147 148 List<Map<String, Object>> tabs = new ArrayList<>(); 149 150 Map<I18nizableText, Map<I18nizableText, List<SkinParameter>>> skinParameters = _getCategorizedParameters(skinDir); 151 152 for (Entry<I18nizableText, Map<I18nizableText, List<SkinParameter>>> categoryEntry : skinParameters.entrySet()) 153 { 154 Map<String, Object> tab = new HashMap<>(); 155 tab.put("role", "tab"); 156 tab.put("label", categoryEntry.getKey()); 157 158 Map<String, Object> tabElmts = new HashMap<>(); 159 160 List<Map<String, Object>> fieldsets = new ArrayList<>(); 161 Map<I18nizableText, List<SkinParameter>> groups = categoryEntry.getValue(); 162 for (Entry<I18nizableText, List<SkinParameter>> groupEntry : groups.entrySet()) 163 { 164 Map<String, Object> fieldset = new HashMap<>(); 165 fieldset.put("role", "fieldset"); 166 fieldset.put("label", groupEntry.getKey()); 167 fieldset.put("elements", _parameters2JsonObject(groupEntry.getValue())); 168 169 fieldsets.add(fieldset); 170 } 171 172 tabElmts.put("fieldsets", fieldsets); 173 tab.put("elements", tabElmts); 174 175 tabs.add(tab); 176 } 177 178 parameters.put("fieldsets", tabs); 179 180 return parameters; 181 } 182 183 private List<SkinParameter> _getParameters(File skinDir) throws ProcessingException 184 { 185 List<SkinParameter> skinParams = new ArrayList<>(); 186 187 ThreadSafeComponentManager<Validator> validatorManager = new ThreadSafeComponentManager<>(); 188 ThreadSafeComponentManager<Enumerator> enumeratorManager = new ThreadSafeComponentManager<>(); 189 190 try 191 { 192 validatorManager.setLogger(LoggerFactory.getLogger(getClass())); 193 validatorManager.contextualize(_context); 194 validatorManager.service(manager); 195 196 enumeratorManager.setLogger(LoggerFactory.getLogger(getClass())); 197 enumeratorManager.contextualize(_context); 198 enumeratorManager.service(manager); 199 200 SkinParameterParser parser = new SkinParameterParser(enumeratorManager, validatorManager, new File (skinDir, "i18n").toURI().toString()); 201 202 Configuration configuration = _getConfigurationModel(skinDir); 203 204 if (configuration != null) 205 { 206 Configuration[] parameterConfigs = configuration.getChild("parameters").getChildren("parameter"); 207 for (Configuration parameterConfig : parameterConfigs) 208 { 209 SkinParameter parameter = parser.parseParameter(manager, "tempskin", parameterConfig); 210 skinParams.add(parameter); 211 } 212 } 213 214 return skinParams; 215 } 216 catch (ConfigurationException | SAXException | IOException e) 217 { 218 throw new ProcessingException(e); 219 } 220 } 221 222 private Map<I18nizableText, Map<I18nizableText, List<SkinParameter>>> _getCategorizedParameters(File skinDir) throws ProcessingException 223 { 224 return _categorize(_getParameters(skinDir)); 225 } 226 227 228 private Map<String, Object> _parameters2JsonObject (List<SkinParameter> skinParameters) throws ProcessingException 229 { 230 Map<String, Object> jsonObject = new LinkedHashMap<>(); 231 232 for (SkinParameter skinParameter : skinParameters) 233 { 234 jsonObject.put(skinParameter.getId(), _parameter2JsonObject(skinParameter)); 235 } 236 237 return jsonObject; 238 } 239 240 private Map<String, Object> _parameter2JsonObject (SkinParameter skinParameter) throws ProcessingException 241 { 242 Map<String, Object> param = new HashMap<>(); 243 244 param.put("label", skinParameter.getLabel()); 245 param.put("description", skinParameter.getDescription()); 246 param.put("type", skinParameter.getType()); 247 248 Validator validator = skinParameter.getValidator(); 249 if (validator != null) 250 { 251 param.put("validation", validator.toJson()); 252 } 253 254 String widget = skinParameter.getWidget(); 255 if (widget != null) 256 { 257 param.put("widget", widget); 258 } 259 260 Map<String, I18nizableText> widgetParameters = skinParameter.getWidgetParameters(); 261 if (widgetParameters != null && widgetParameters.size() > 0) 262 { 263 param.put("widget-params", skinParameter.getWidgetParameters()); 264 } 265 266 Object defaultValue = skinParameter.getDefaultValue(); 267 if (defaultValue != null) 268 { 269 param.put("default-value", skinParameter.getDefaultValue()); 270 } 271 272 Enumerator enumerator = skinParameter.getEnumerator(); 273 274 if (enumerator != null) 275 { 276 try 277 { 278 List<Map<String, Object>> options = new ArrayList<>(); 279 280 for (Map.Entry<Object, I18nizableText> entry : enumerator.getEntries().entrySet()) 281 { 282 String valueAsString = ParameterHelper.valueToString(entry.getKey()); 283 I18nizableText entryLabel = entry.getValue(); 284 285 Map<String, Object> option = new HashMap<>(); 286 option.put("label", entryLabel != null ? entryLabel : valueAsString); 287 option.put("value", valueAsString); 288 options.add(option); 289 } 290 291 param.put("enumeration", options); 292 } 293 catch (Exception e) 294 { 295 throw new ProcessingException("Unable to enumerate entries with enumerator: " + enumerator, e); 296 } 297 } 298 299 return param; 300 } 301 302 private Configuration _getConfigurationModel(File skinDir) throws ConfigurationException, SAXException, IOException 303 { 304 File configFile = new File(skinDir, "stylesheets" + File.separator + "config" + File.separator + "config-model.xml"); 305 306 if (configFile.exists()) 307 { 308 try (FileInputStream is = new FileInputStream(configFile)) 309 { 310 DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder(); 311 return builder.build(is); 312 } 313 } 314 315 // There is no parameter 316 return null; 317 } 318 319 /** 320 * Organize a collection of skin parameters by categories and groups. 321 * @param parameters a collection of skin parameters. 322 * @return a Map of parameters sorted first by category then group. 323 */ 324 protected Map<I18nizableText, Map<I18nizableText, List<SkinParameter>>> _categorize(Collection<SkinParameter> parameters) 325 { 326 Map<I18nizableText, Map<I18nizableText, List<SkinParameter>>> categories = new HashMap<>(); 327 328 // Classify parameters by groups and categories 329 for (SkinParameter parameter : parameters) 330 { 331 I18nizableText categoryName = parameter.getDisplayCategory(); 332 I18nizableText groupName = parameter.getDisplayGroup(); 333 334 // Get the map of groups of the category 335 Map<I18nizableText, List<SkinParameter>> category = categories.get(categoryName); 336 if (category == null) 337 { 338 category = new TreeMap<>(new I18nizableTextComparator()); 339 categories.put(categoryName, category); 340 } 341 342 // Get the map of parameters of the group 343 List<SkinParameter> group = category.get(groupName); 344 if (group == null) 345 { 346 group = new ArrayList<>(); 347 category.put(groupName, group); 348 } 349 350 group.add(parameter); 351 } 352 353 return categories; 354 } 355 356 class I18nizableTextComparator implements Comparator<I18nizableText> 357 { 358 @Override 359 public int compare(I18nizableText t1, I18nizableText t2) 360 { 361 return t1.toString().compareTo(t2.toString()); 362 } 363 } 364 365 /** 366 * Parser for {@link SkinParameter} parameter. 367 */ 368 class SkinParameterParser extends AbstractParameterParser<SkinParameter, ParameterType> 369 { 370 private String _i18nCatalogueLocation; 371 372 public SkinParameterParser(ThreadSafeComponentManager<Enumerator> enumeratorManager, ThreadSafeComponentManager<Validator> validatorManager, String i18nCatalogueLocation) 373 { 374 super(enumeratorManager, validatorManager); 375 _i18nCatalogueLocation = i18nCatalogueLocation; 376 } 377 378 @Override 379 protected SkinParameter _createParameter(Configuration parameterConfig) throws ConfigurationException 380 { 381 return new SkinParameter(); 382 } 383 384 @Override 385 protected String _parseId(Configuration parameterConfig) throws ConfigurationException 386 { 387 return parameterConfig.getAttribute("id"); 388 } 389 390 @Override 391 protected ParameterType _parseType(Configuration parameterConfig) throws ConfigurationException 392 { 393 try 394 { 395 return ParameterType.valueOf(parameterConfig.getAttribute("type").toUpperCase()); 396 } 397 catch (IllegalArgumentException e) 398 { 399 throw new ConfigurationException("Invalid parameter type", parameterConfig, e); 400 } 401 } 402 403 @Override 404 protected I18nizableText _parseI18nizableText(Configuration config, String pluginName, String name) throws ConfigurationException 405 { 406 return I18nizableText.parseI18nizableText(config.getChild(name), _i18nCatalogueLocation, "messages", null); 407 } 408 409 @Override 410 protected Object _parseDefaultValue(Configuration parameterConfig, SkinParameter parameter) 411 { 412 String value; 413 414 Configuration childNode = parameterConfig.getChild("default-value", false); 415 if (childNode == null) 416 { 417 value = null; 418 } 419 else 420 { 421 value = childNode.getValue(""); 422 } 423 424 return ParameterHelper.castValue(value, parameter.getType()); 425 } 426 427 @Override 428 protected void _additionalParsing(ServiceManager smanager, String pluginName, Configuration parameterConfig, String parameterId, SkinParameter parameter) throws ConfigurationException 429 { 430 super._additionalParsing(smanager, pluginName, parameterConfig, parameterId, parameter); 431 432 parameter.setId(parameterId); 433 parameter.setDisplayCategory(_parseI18nizableText(parameterConfig, pluginName, "category")); 434 parameter.setDisplayGroup(_parseI18nizableText(parameterConfig, pluginName, "group")); 435 } 436 } 437 438 class SkinConfigHandler extends DefaultHandler 439 { 440 441 // The object being constructed 442 private Map<String, String> _variables; 443 444 // The object being constructed 445 private String _variable; 446 447 // current characters from SAX events 448 private StringBuffer _currentString; 449 450 public Map<String, String> getVariables() 451 { 452 return _variables; 453 } 454 455 @Override 456 public void startDocument() throws SAXException 457 { 458 _variables = new HashMap<>(); 459 _variable = null; 460 } 461 462 @Override 463 public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException 464 { 465 if ("xsl:variable".equals(qName)) 466 { 467 _variable = attributes.getValue("name"); 468 469 _currentString = new StringBuffer(); 470 } 471 } 472 473 @Override 474 public void characters(char[] ch, int start, int length) throws SAXException 475 { 476 if (_variable != null) 477 { 478 _currentString.append(ch, start, length); 479 } 480 } 481 482 @Override 483 public void endElement(String uri, String localName, String qName) throws SAXException 484 { 485 if ("xsl:variable".equals(qName) && _variable != null) 486 { 487 _variables.put(_variable, _currentString.toString()); 488 489 _variable = null; 490 } 491 } 492 } 493}