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; 017 018import java.io.BufferedOutputStream; 019import java.io.File; 020import java.io.FileOutputStream; 021import java.io.IOException; 022import java.io.OutputStream; 023import java.util.ArrayList; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027import java.util.Map.Entry; 028import java.util.Properties; 029import java.util.stream.Collectors; 030 031import javax.xml.transform.OutputKeys; 032import javax.xml.transform.Result; 033import javax.xml.transform.TransformerConfigurationException; 034import javax.xml.transform.TransformerFactory; 035import javax.xml.transform.sax.SAXTransformerFactory; 036import javax.xml.transform.sax.TransformerHandler; 037import javax.xml.transform.stream.StreamResult; 038 039import org.apache.avalon.framework.component.Component; 040import org.apache.avalon.framework.logger.AbstractLogEnabled; 041import org.apache.avalon.framework.service.ServiceException; 042import org.apache.avalon.framework.service.ServiceManager; 043import org.apache.avalon.framework.service.Serviceable; 044import org.apache.cocoon.ProcessingException; 045import org.apache.cocoon.xml.AttributesImpl; 046import org.apache.cocoon.xml.XMLUtils; 047import org.apache.commons.io.FileUtils; 048import org.apache.xml.serializer.OutputPropertiesFactory; 049import org.xml.sax.SAXException; 050 051import org.ametys.core.ui.Callable; 052import org.ametys.plugins.repository.AmetysObjectIterable; 053import org.ametys.plugins.repository.metadata.UnknownMetadataException; 054import org.ametys.runtime.parameter.ParameterHelper; 055import org.ametys.runtime.util.AmetysHomeHelper; 056import org.ametys.web.cocoon.I18nTransformer; 057import org.ametys.web.cocoon.I18nUtils; 058import org.ametys.web.repository.site.Site; 059import org.ametys.web.repository.site.SiteManager; 060 061/** 062 * DAO for manipulating skins 063 */ 064public class SkinDAO extends AbstractLogEnabled implements Serviceable, Component 065{ 066 /** The avalon role*/ 067 public static final String ROLE = SkinDAO.class.getName(); 068 069 private static final String __LICENCE = "\n" 070 + " Copyright 2012 Anyware Services\n" 071 + "\n" 072 + " Licensed under the Apache License, Version 2.0 (the \"License\");\n" 073 + " you may not use this file except in compliance with the License.\n" 074 + " You may obtain a copy of the License at\n" 075 + "\n" 076 + " http://www.apache.org/licenses/LICENSE-2.0\n" 077 + "\n" 078 + " Unless required by applicable law or agreed to in writing, software\n" 079 + " distributed under the License is distributed on an \"AS IS\" BASIS,\n" 080 + " WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" 081 + " See the License for the specific language governing permissions and\n" 082 + " limitations under the License.\n" 083 + "\n"; 084 085 086 private SkinsManager _skinsManager; 087 private SkinModelsManager _modelsManager; 088 private SiteManager _siteManager; 089 private I18nUtils _i18nUtils; 090 091 @Override 092 public void service(ServiceManager manager) throws ServiceException 093 { 094 _skinsManager = (SkinsManager) manager.lookup(SkinsManager.ROLE); 095 _modelsManager = (SkinModelsManager) manager.lookup(SkinModelsManager.ROLE); 096 _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE); 097 _i18nUtils = (I18nUtils) manager.lookup(org.ametys.core.util.I18nUtils.ROLE); 098 } 099 100 /** 101 * Retrieve informations on a skin 102 * @param skinId The skin id 103 * @return the informations of a skin 104 */ 105 @Callable 106 public Map<String, Object> getSkin(String skinId) 107 { 108 Map<String, Object> result = new HashMap<>(); 109 110 Skin skin = _skinsManager.getSkin(skinId); 111 112 if (skin != null) 113 { 114 result.put("id", skin.getId()); 115 result.put("label", skin.getLabel()); 116 result.put("inUse", isInUse(skinId)); 117 118 String modelName = _modelsManager.getModelOfSkin(skin); 119 result.put("model", modelName); 120 } 121 122 return result; 123 } 124 125 /** 126 * Determines if a skin exists 127 * @param skinId The skin id 128 * @return true if skin exists. 129 * @throws ProcessingException if an error occurs 130 */ 131 @Callable 132 public boolean skinExists (String skinId) throws ProcessingException 133 { 134 return _skinsManager.getSkins().contains(skinId); 135 } 136 137 /** 138 * Determines if a skin is currently used by one or more sites 139 * @param skinId The skin id 140 * @return true if skin is currently in use 141 */ 142 @Callable 143 public boolean isInUse (String skinId) 144 { 145 AmetysObjectIterable<Site> sites = _siteManager.getSites(); 146 for (Site site : sites) 147 { 148 try 149 { 150 if (skinId.equals(site.getSkinId())) 151 { 152 return true; 153 } 154 } 155 catch (UnknownMetadataException e) 156 { 157 // No skin is defined for this site 158 } 159 160 } 161 return false; 162 } 163 164 /** 165 * Retrieve the list of available skins 166 * @return a map of skins 167 * @throws ProcessingException if an error occurs 168 */ 169 @Callable 170 public Map<String, Object> getSkins() throws ProcessingException 171 { 172 Map<String, Object> result = new HashMap<>(); 173 174 result.put("skins", _skins2JsonObject()); 175 176 return result; 177 } 178 179 private List<Object> _skins2JsonObject() 180 { 181 List<Object> skinsList = new ArrayList<>(); 182 Map<String, List<Site>> skins = new HashMap<>(); 183 for (String id : _skinsManager.getSkins()) 184 { 185 skins.put(id, new ArrayList<Site>()); 186 } 187 188 AmetysObjectIterable<Site> sites = _siteManager.getSites(); 189 for (Site site : sites) 190 { 191 try 192 { 193 String skinId = site.getSkinId(); 194 List<Site> skin = skins.get(skinId); 195 196 if (skin != null) 197 { 198 skin.add(site); 199 } 200 } 201 catch (UnknownMetadataException e) 202 { 203 // Nothing 204 } 205 } 206 207 for (String id : skins.keySet()) 208 { 209 skinsList.add(_skin2JsonObject(id, skins.get(id))); 210 } 211 212 return skinsList; 213 } 214 215 private Map<String, Object> _skin2JsonObject(String id, List<Site> skinSites) 216 { 217 Map<String, Object> jsonSkin = new HashMap<>(); 218 219 Skin skin = _skinsManager.getSkin(id); 220 221 jsonSkin.put("id", skin.getId()); 222 jsonSkin.put("label", skin.getLabel()); 223 jsonSkin.put("description", skin.getDescription()); 224 jsonSkin.put("iconLarge", skin.getLargeImage()); 225 jsonSkin.put("iconSmall", skin.getSmallImage()); 226 jsonSkin.put("nbSites", skinSites.size()); 227 jsonSkin.put("sites", skinSites.stream() 228 .map(site -> site.getTitle() + " (" + site.getName() + ")") 229 .collect(Collectors.joining(", "))); 230 231 String modelName = _modelsManager.getModelOfSkin(skin); 232 if (modelName != null) 233 { 234 SkinModel model = _modelsManager.getModel(modelName); 235 if (model != null) 236 { 237 Map<String, Object> skinModel = new HashMap<>(); 238 239 skinModel.put("id", modelName); 240 skinModel.put("name", model.getLabel()); 241 242 jsonSkin.put("model", skinModel); 243 } 244 } 245 246 return jsonSkin; 247 } 248 249 250 251 /** 252 * This action receive a form with the "importfile" zip file as an exported skin. 253 * Replace existing skin 254 * @param skinName The skin name 255 * @param tmpDirPath The directory where the zip was uploaded 256 * @param values the configuration's values. Can be empty. 257 * @return The skin name 258 * @throws SAXException if an error occurs during configuration file creation 259 * @throws IOException if an error occurs while manipulating files 260 * @throws TransformerConfigurationException if an error occurs during configuration file creation 261 */ 262 @Callable 263 public Map<String, Object> importSkin(String skinName, String tmpDirPath, Map<String, Object> values) throws TransformerConfigurationException, IOException, SAXException 264 { 265 Map<String, Object> result = new HashMap<>(); 266 267 File ametysTmpDir = AmetysHomeHelper.getAmetysHomeTmp(); 268 File tmpDir = new File(ametysTmpDir, tmpDirPath.replace('/', File.separatorChar)); 269 if (tmpDir.isDirectory()) 270 { 271 File rootLocation = new File(_skinsManager.getSkinsLocation()); 272 273 if (!values.isEmpty()) 274 { 275 //Map<String, Errors> errors = new HashMap<>(); 276 _createConfigFile(tmpDir, values/*, errors*/); 277 278 /*if (errors.size() > 0) 279 { 280 List<Map<String, Object>> allErrors = new ArrayList<>(); 281 282 for (Map.Entry<String, Errors> entry : errors.entrySet()) 283 { 284 Map<String, Object> error = new HashMap<>(); 285 error.put("name", entry.getKey()); 286 287 List<I18nizableText> errorMessages = entry.getValue().getErrors(); 288 error.put("errorMessages", errorMessages); 289 290 allErrors.add(error); 291 } 292 293 result.put("errors", allErrors); 294 return result; 295 }*/ 296 } 297 298 // If exists: remove. 299 File skinDir = new File(rootLocation, skinName); 300 if (skinDir.exists()) 301 { 302 FileUtils.deleteDirectory(skinDir); 303 } 304 305 // Move to skins 306 FileUtils.moveDirectory(tmpDir, skinDir); 307 308 _i18nUtils.reloadCatalogues(); 309 I18nTransformer.needsReload(); 310 } 311 312 result.put("skinId", skinName); 313 return result; 314 } 315 316 /** 317 * Configure a skin 318 * @param skinName the skin name 319 * @param values the configuration's values 320 * @return A map with "errors" key that is a map <errorName> <errorMessage> 321 * @throws SAXException if an error occurs during configuration file creation 322 * @throws IOException if an error occurs during configuration file creation 323 * @throws TransformerConfigurationException if an error occurs during configuration file creation 324 */ 325 @Callable 326 public Map<String, Object> configureSkin (String skinName, Map<String, Object> values) throws TransformerConfigurationException, IOException, SAXException 327 { 328 Map<String, Object> result = new HashMap<>(); 329 330 Skin skin = _skinsManager.getSkin(skinName); 331 332 File skinDir = new File (skin.getLocation()); 333 334 File configFile = new File(skinDir, "stylesheets" + File.separator + "config" + File.separator + "config.xsl"); 335 if (configFile.exists()) 336 { 337 FileUtils.deleteQuietly(configFile); 338 } 339 340 // Map<String, Errors> errors = new HashMap<>(); 341 _createConfigFile(skinDir, values/*, errors*/); 342 343 /*if (errors.size() > 0) 344 { 345 List<Map<String, Object>> allErrors = new ArrayList<>(); 346 347 for (Map.Entry<String, Errors> entry : errors.entrySet()) 348 { 349 Map<String, Object> error = new HashMap<>(); 350 error.put("name", entry.getKey()); 351 352 List<I18nizableText> errorMessages = entry.getValue().getErrors(); 353 error.put("errorMessages", errorMessages); 354 355 allErrors.add(error); 356 } 357 358 result.put("errors", allErrors); 359 return result; 360 }*/ 361 362 _i18nUtils.reloadCatalogues(); 363 I18nTransformer.needsReload(); 364 365 result.put("skinId", skinName); 366 return result; 367 } 368 369 private void _createConfigFile(File skinDir, Map<String, Object> values/*, Map<String, Errors> allErrors*/) throws IOException, SAXException, TransformerConfigurationException 370 { 371 // TODO Handle server errors 372 373 File configFile = new File(skinDir, "stylesheets" + File.separator + "config" + File.separator + "config.xsl"); 374 375 SAXTransformerFactory factory = (SAXTransformerFactory) TransformerFactory.newInstance(); 376 TransformerHandler handler = factory.newTransformerHandler(); 377 378 try (OutputStream os = new BufferedOutputStream(new FileOutputStream(configFile))) 379 { 380 Result result = new StreamResult(os); 381 382 Properties format = new Properties(); 383 format.put(OutputKeys.METHOD, "xml"); 384 format.put(OutputKeys.ENCODING, "UTF-8"); 385 format.put(OutputKeys.INDENT, "yes"); 386 format.put(OutputKeys.OMIT_XML_DECLARATION, "no"); 387 format.put(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "4"); 388 389 handler.setResult(result); 390 handler.getTransformer().setOutputProperties(format); 391 392 handler.startDocument(); 393 394 AttributesImpl attrs = new AttributesImpl(); 395 attrs.addCDATAAttribute("version", "1.0"); 396 attrs.addCDATAAttribute("xmlns:xsl", "http://www.w3.org/1999/XSL/Transform"); 397 398 handler.comment(__LICENCE.toCharArray(), 0, __LICENCE.length()); 399 XMLUtils.startElement(handler, "xsl:stylesheet", attrs); 400 401 for (Entry<String, Object> variable : values.entrySet()) 402 { 403 attrs.clear(); 404 attrs.addCDATAAttribute("name", variable.getKey()); 405 406 XMLUtils.createElement(handler, "xsl:variable", attrs, ParameterHelper.valueToString(variable.getValue())); 407 } 408 409 XMLUtils.endElement(handler, "xsl:stylesheet"); 410 411 handler.endDocument(); 412 } 413 } 414 415 /** 416 * Duplicate an existing skin 417 * @param skinId The new skin id 418 * @param originalSkinId The original skin id 419 * @return An error message, or null if successful 420 * @throws IOException if an I/O exception occurs during copy 421 */ 422 @Callable 423 public String copySkin(String skinId, String originalSkinId) throws IOException 424 { 425 // Check if exists 426 if (_skinsManager.getSkins().contains(skinId)) 427 { 428 return "already-exists"; 429 } 430 431 File srcDir = new File(_skinsManager.getSkinsLocation() + File.separator + originalSkinId); 432 File destDir = new File(_skinsManager.getSkinsLocation() + File.separator + skinId); 433 434 FileUtils.copyDirectory(srcDir, destDir, new ModelFileFilter(srcDir), false); 435 436 _i18nUtils.reloadCatalogues(); 437 I18nTransformer.needsReload(); 438 439 return null; 440 } 441 442 /** 443 * Delete a skin 444 * @param skinId The skin id 445 * @return the skin id 446 * @throws IOException if an I/O exception occurs during deletion 447 */ 448 @Callable 449 public String deleteSkin(String skinId) throws IOException 450 { 451 Skin skin = _skinsManager.getSkin(skinId); 452 File file = skin.getFile(); 453 454 if (file.exists()) 455 { 456 FileUtils.deleteDirectory(file); 457 } 458 459 return skinId; 460 } 461}