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 = ParameterHelper.toJSON(skinParameter); 243 return param; 244 } 245 246 private Configuration _getConfigurationModel(File skinDir) throws ConfigurationException, SAXException, IOException 247 { 248 File configFile = new File(skinDir, "stylesheets" + File.separator + "config" + File.separator + "config-model.xml"); 249 250 if (configFile.exists()) 251 { 252 try (FileInputStream is = new FileInputStream(configFile)) 253 { 254 DefaultConfigurationBuilder builder = new DefaultConfigurationBuilder(); 255 return builder.build(is); 256 } 257 } 258 259 // There is no parameter 260 return null; 261 } 262 263 /** 264 * Organize a collection of skin parameters by categories and groups. 265 * @param parameters a collection of skin parameters. 266 * @return a Map of parameters sorted first by category then group. 267 */ 268 protected Map<I18nizableText, Map<I18nizableText, List<SkinParameter>>> _categorize(Collection<SkinParameter> parameters) 269 { 270 Map<I18nizableText, Map<I18nizableText, List<SkinParameter>>> categories = new HashMap<>(); 271 272 // Classify parameters by groups and categories 273 for (SkinParameter parameter : parameters) 274 { 275 I18nizableText categoryName = parameter.getDisplayCategory(); 276 I18nizableText groupName = parameter.getDisplayGroup(); 277 278 // Get the map of groups of the category 279 Map<I18nizableText, List<SkinParameter>> category = categories.get(categoryName); 280 if (category == null) 281 { 282 category = new TreeMap<>(new I18nizableTextComparator()); 283 categories.put(categoryName, category); 284 } 285 286 // Get the map of parameters of the group 287 List<SkinParameter> group = category.get(groupName); 288 if (group == null) 289 { 290 group = new ArrayList<>(); 291 category.put(groupName, group); 292 } 293 294 group.add(parameter); 295 } 296 297 return categories; 298 } 299 300 class I18nizableTextComparator implements Comparator<I18nizableText> 301 { 302 @Override 303 public int compare(I18nizableText t1, I18nizableText t2) 304 { 305 return t1.toString().compareTo(t2.toString()); 306 } 307 } 308 309 /** 310 * Parser for {@link SkinParameter} parameter. 311 */ 312 class SkinParameterParser extends AbstractParameterParser<SkinParameter, ParameterType> 313 { 314 private String _i18nCatalogueLocation; 315 316 public SkinParameterParser(ThreadSafeComponentManager<Enumerator> enumeratorManager, ThreadSafeComponentManager<Validator> validatorManager, String i18nCatalogueLocation) 317 { 318 super(enumeratorManager, validatorManager); 319 _i18nCatalogueLocation = i18nCatalogueLocation; 320 } 321 322 @Override 323 protected SkinParameter _createParameter(Configuration parameterConfig) throws ConfigurationException 324 { 325 return new SkinParameter(); 326 } 327 328 @Override 329 protected String _parseId(Configuration parameterConfig) throws ConfigurationException 330 { 331 return parameterConfig.getAttribute("id"); 332 } 333 334 @Override 335 protected ParameterType _parseType(Configuration parameterConfig) throws ConfigurationException 336 { 337 try 338 { 339 return ParameterType.valueOf(parameterConfig.getAttribute("type").toUpperCase()); 340 } 341 catch (IllegalArgumentException e) 342 { 343 throw new ConfigurationException("Invalid parameter type", parameterConfig, e); 344 } 345 } 346 347 @Override 348 protected I18nizableText _parseI18nizableText(Configuration config, String pluginName, String name) throws ConfigurationException 349 { 350 return I18nizableText.parseI18nizableText(config.getChild(name), _i18nCatalogueLocation, "messages", null); 351 } 352 353 @Override 354 protected Object _parseDefaultValue(Configuration parameterConfig, SkinParameter parameter) 355 { 356 String value; 357 358 Configuration childNode = parameterConfig.getChild("default-value", false); 359 if (childNode == null) 360 { 361 value = null; 362 } 363 else 364 { 365 value = childNode.getValue(""); 366 } 367 368 return ParameterHelper.castValue(value, parameter.getType()); 369 } 370 371 @Override 372 protected void _additionalParsing(ServiceManager smanager, String pluginName, Configuration parameterConfig, String parameterId, SkinParameter parameter) throws ConfigurationException 373 { 374 super._additionalParsing(smanager, pluginName, parameterConfig, parameterId, parameter); 375 376 parameter.setId(parameterId); 377 parameter.setDisplayCategory(_parseI18nizableText(parameterConfig, pluginName, "category")); 378 parameter.setDisplayGroup(_parseI18nizableText(parameterConfig, pluginName, "group")); 379 } 380 } 381 382 class SkinConfigHandler extends DefaultHandler 383 { 384 385 // The object being constructed 386 private Map<String, String> _variables; 387 388 // The object being constructed 389 private String _variable; 390 391 // current characters from SAX events 392 private StringBuffer _currentString; 393 394 public Map<String, String> getVariables() 395 { 396 return _variables; 397 } 398 399 @Override 400 public void startDocument() throws SAXException 401 { 402 _variables = new HashMap<>(); 403 _variable = null; 404 } 405 406 @Override 407 public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException 408 { 409 if ("xsl:variable".equals(qName)) 410 { 411 _variable = attributes.getValue("name"); 412 413 _currentString = new StringBuffer(); 414 } 415 } 416 417 @Override 418 public void characters(char[] ch, int start, int length) throws SAXException 419 { 420 if (_variable != null) 421 { 422 _currentString.append(ch, start, length); 423 } 424 } 425 426 @Override 427 public void endElement(String uri, String localName, String qName) throws SAXException 428 { 429 if ("xsl:variable".equals(qName) && _variable != null) 430 { 431 _variables.put(_variable, _currentString.toString()); 432 433 _variable = null; 434 } 435 } 436 } 437}