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 */ 016 017package org.ametys.skinfactory.model; 018 019import java.io.IOException; 020import java.nio.file.Files; 021import java.nio.file.Path; 022import java.text.DateFormat; 023import java.text.SimpleDateFormat; 024import java.util.ArrayList; 025import java.util.Date; 026import java.util.HashMap; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030 031import org.apache.avalon.framework.component.Component; 032import org.apache.avalon.framework.service.ServiceException; 033import org.apache.avalon.framework.service.ServiceManager; 034import org.apache.avalon.framework.service.Serviceable; 035import org.apache.cocoon.ProcessingException; 036import org.apache.commons.io.FileUtils; 037 038import org.ametys.core.ui.Callable; 039import org.ametys.core.util.path.PathUtils; 040import org.ametys.plugins.skincommons.SkinEditionHelper; 041import org.ametys.runtime.plugin.component.AbstractLogEnabled; 042import org.ametys.runtime.util.AmetysHomeHelper; 043import org.ametys.skinfactory.SkinFactoryComponent; 044import org.ametys.skinfactory.filefilter.FileFilter; 045import org.ametys.web.cocoon.I18nTransformer; 046import org.ametys.web.cocoon.I18nUtils; 047import org.ametys.web.skin.Skin; 048import org.ametys.web.skin.SkinDAO; 049import org.ametys.web.skin.SkinModel; 050import org.ametys.web.skin.SkinModelsManager; 051import org.ametys.web.skin.SkinsManager; 052 053/** 054 * Component for interact with a skin model 055 */ 056public class SkinModelDAO extends AbstractLogEnabled implements Serviceable, Component 057{ 058 private static final DateFormat _DATE_FORMAT = new SimpleDateFormat("yyyyMMdd-HHmm"); 059 060 061 private SkinsManager _skinsManager; 062 private SkinModelsManager _modelsManager; 063 private SkinFactoryComponent _skinFactoryManager; 064 private SkinEditionHelper _skinHelper; 065 private SkinDAO _skinDAO; 066 private I18nUtils _i18nUtils; 067 068 @Override 069 public void service(ServiceManager manager) throws ServiceException 070 { 071 _skinsManager = (SkinsManager) manager.lookup(SkinsManager.ROLE); 072 _modelsManager = (SkinModelsManager) manager.lookup(SkinModelsManager.ROLE); 073 _skinFactoryManager = (SkinFactoryComponent) manager.lookup(SkinFactoryComponent.ROLE); 074 _skinHelper = (SkinEditionHelper) manager.lookup(SkinEditionHelper.ROLE); 075 _skinDAO = (SkinDAO) manager.lookup(SkinDAO.ROLE); 076 _i18nUtils = (I18nUtils) manager.lookup(org.ametys.core.util.I18nUtils.ROLE); 077 } 078 079 /** 080 * Retrieve informations on a skin 081 * @param modelId The skin id 082 * @return the informations of a skin 083 */ 084 @Callable 085 public Map<String, Object> getModel(String modelId) 086 { 087 Map<String, Object> result = new HashMap<>(); 088 089 SkinModel model = _modelsManager.getModel(modelId); 090 091 if (model != null) 092 { 093 result.put("name", model.getId()); 094 result.put("title", model.getLabel()); 095 result.put("isModifiable", model.isModifiable()); 096 } 097 098 return result; 099 } 100 101 /** 102 * Retrieve the list of models and skins available 103 * @param includeAbstract Should include abstract skins 104 * @return a map of skins and models 105 */ 106 @Callable 107 public Map<String, Object> getSkinsAndModels(boolean includeAbstract) 108 { 109 Map<String, Object> result = _skinDAO.getSkins(includeAbstract); 110 111 result.put("models", _models2JsonObject()); 112 113 return result; 114 } 115 116 private List<Object> _models2JsonObject() 117 { 118 List<Object> modelsList = new ArrayList<>(); 119 Set<String> models = _modelsManager.getModels(); 120 for (String modelName : models) 121 { 122 Map<String, Object> jsonModel = new HashMap<>(); 123 SkinModel model = _modelsManager.getModel(modelName); 124 125 jsonModel.put("id", modelName); 126 jsonModel.put("label", model.getLabel()); 127 jsonModel.put("description", model.getDescription()); 128 jsonModel.put("iconLarge", model.getLargeImage()); 129 jsonModel.put("iconSmall", model.getSmallImage()); 130 131 modelsList.add(jsonModel); 132 } 133 134 return modelsList; 135 } 136 137 /** 138 * Determines if a model exists 139 * @param modelId The model id 140 * @return true if model exists. 141 * @throws ProcessingException if something goes wrong when retrieving the list of models 142 */ 143 @Callable 144 public boolean modelExists (String modelId) throws ProcessingException 145 { 146 return _modelsManager.getModels().contains(modelId); 147 } 148 149 /** 150 * Import a model from a zip file 151 * @param modelName The name of the new model 152 * @param tmpDirPath the tmp dir path where the zip has been uploaded 153 * @return The model name 154 * @throws IOException if something goes wrong when manipulating files 155 */ 156 @Callable 157 public String importModel(String modelName, String tmpDirPath) throws IOException 158 { 159 Path tmpDir = AmetysHomeHelper.getAmetysHomeTmp().toPath().resolve(tmpDirPath); 160 161 if (Files.isDirectory(tmpDir)) 162 { 163 // If exists: remove. 164 SkinModel model = _modelsManager.getModel(modelName); 165 if (model != null) 166 { 167 if (model.isModifiable()) 168 { 169 PathUtils.deleteDirectory(model.getPath()); 170 } 171 else 172 { 173 throw new IllegalStateException("The skin model '" + modelName + "' already exists and is not modifiable and thus cannot be replaced."); 174 } 175 } 176 177 // Move to models 178 Path rootLocation = _modelsManager.getLocalModelsLocation(); 179 PathUtils.moveDirectory(tmpDir, rootLocation.resolve(modelName)); 180 181 _i18nUtils.reloadCatalogues(); 182 I18nTransformer.needsReload(); 183 } 184 185 return modelName; 186 } 187 188 /** 189 * Generate a new skin from a model 190 * @param skinId The new skin id 191 * @param modelId The model 192 * @return An error message, or null on success 193 * @throws IOException if an error occurs when manipulating files 194 * @throws ProcessingException if an exception occurred during the generation processs 195 */ 196 @Callable 197 public String generateSkin(String skinId, String modelId) throws IOException, ProcessingException 198 { 199 // Check if exists 200 if (_skinsManager.getSkins().contains(skinId)) 201 { 202 return "already-exists"; 203 } 204 205 Path modelDir = _modelsManager.getModel(modelId).getPath(); 206 Path skinDir = _skinsManager.getLocalSkinsLocation().resolve(skinId); 207 208 // Copy the model 209 PathUtils.copyDirectory(modelDir, skinDir, FileFilter.getModelFilter(modelDir), false); 210 211 try 212 { 213 Skin skin = _skinsManager.getSkin(skinId); 214 215 // Create model.xml file 216 _modelsManager.generateModelFile(skinDir, modelId); 217 218 SkinModel model = _modelsManager.getModel(modelId); 219 String defaultColorTheme = model.getDefaultColorTheme(); 220 if (defaultColorTheme != null) 221 { 222 _skinFactoryManager.saveColorTheme(skin.getRawPath(), defaultColorTheme); 223 } 224 225 // Apply all parameters 226 _skinFactoryManager.applyModelParameters(modelId, skin.getRawPath()); 227 228 I18nTransformer.needsReload(); 229 _i18nUtils.reloadCatalogues(); 230 } 231 catch (Exception e) 232 { 233 // Delete skin directory if the generation failed 234 FileUtils.deleteDirectory(skinDir.toFile()); 235 236 throw new ProcessingException("The generation of skin failed", e); 237 238 } 239 240 return null; 241 } 242 243 /** 244 * Apply the model to all its skins 245 * @param modelId The model id 246 * @return The set of modified skins id 247 * @throws IOException if an error occurs when manipulating files 248 */ 249 @Callable 250 public Map<String, Object> applyModelToAll(String modelId) throws IOException 251 { 252 Map<String, Object> result = new HashMap<>(); 253 254 result.put("modifiedSkins", new ArrayList<Map<String, Object>>()); 255 result.put("unmodifiedSkins", new ArrayList<Map<String, Object>>()); 256 result.put("unmodifiableSkins", new ArrayList<Map<String, Object>>()); 257 258 Set<String> skins = _skinsManager.getSkins(); 259 260 for (String skinId : skins) 261 { 262 Skin skin = _skinsManager.getSkin(skinId); 263 if (modelId.equals(_modelsManager.getModelOfSkin(skin))) 264 { 265 if (!skin.isModifiable()) 266 { 267 @SuppressWarnings("unchecked") 268 List<Map<String, Object>> unmodifiableSkins = (List<Map<String, Object>>) result.get("unmodifiableSkins"); 269 unmodifiableSkins.add(_getSkinProperty(skin)); 270 } 271 else if (applyModel(skin, modelId)) 272 { 273 @SuppressWarnings("unchecked") 274 List<Map<String, Object>> modifiedSkins = (List<Map<String, Object>>) result.get("modifiedSkins"); 275 modifiedSkins.add(_getSkinProperty(skin)); 276 } 277 else 278 { 279 @SuppressWarnings("unchecked") 280 List<Map<String, Object>> unmodifiedSkins = (List<Map<String, Object>>) result.get("unmodifiedSkins"); 281 unmodifiedSkins.add(_getSkinProperty(skin)); 282 } 283 } 284 } 285 286 return result; 287 } 288 289 private Map<String, Object> _getSkinProperty(Skin skin) 290 { 291 Map<String, Object> info = new HashMap<>(); 292 info.put("name", skin.getId()); 293 info.put("label", skin.getLabel()); 294 return info; 295 } 296 297 /** 298 * Apply model to the skin 299 * @param skinId The skin id 300 * @param modelId The id of model 301 * @return true if the model was applyed successfully 302 * @throws IOException if an error occurs when manipulating files 303 */ 304 @Callable 305 public boolean applyModel(String skinId, String modelId) throws IOException 306 { 307 Skin skin = _skinsManager.getSkin(skinId); 308 309 return applyModel(skin, modelId); 310 } 311 312 /** 313 * Apply model to the skin 314 * @param skin The skin 315 * @param modelId The id of model 316 * @return true if the model was applyed successfully 317 * @throws IOException if an error occurs when manipulating files 318 */ 319 protected boolean applyModel(Skin skin, String modelId) throws IOException 320 { 321 if (!skin.isModifiable()) 322 { 323 throw new IllegalStateException("The skin '" + skin.getId() + "' is not modifiable and thus the model can not be applied."); 324 } 325 326 Path skinDir = skin.getRawPath(); 327 328 // Prepare skin in temporary file 329 Path tmpDir = skinDir.getParent().resolve(skin.getId() + "." + _DATE_FORMAT.format(new Date())); 330 331 // Copy the model 332 Path modelDir = _modelsManager.getModel(modelId).getPath(); 333 PathUtils.copyDirectory(modelDir, tmpDir, FileFilter.getModelFilter(modelDir), false); 334 335 // Copy upload images if exists 336 Path uploadDir = skinDir.resolve("model/_uploads"); 337 if (Files.exists(uploadDir)) 338 { 339 Path tmpUploadDir = tmpDir.resolve("model/_uploads"); 340 Files.createDirectories(tmpUploadDir); 341 FileUtils.copyDirectory(uploadDir.toFile(), tmpUploadDir.toFile()); 342 } 343 344 // Copy model.xml file 345 Path xmlFile = skinDir.resolve("model.xml"); 346 FileUtils.copyFileToDirectory(xmlFile.toFile(), tmpDir.toFile()); 347 Path tmpXmlFile = tmpDir.resolve("model.xml"); 348 349 // Apply parameters 350 _skinFactoryManager.applyModelParameters(modelId, tmpDir); 351 _skinFactoryManager.updateHash(tmpXmlFile, _modelsManager.getModelHash(modelId)); 352 353 if (!_skinHelper.deleteQuicklyDirectory(skinDir)) 354 { 355 getLogger().error("Cannot delete skin directory {}", skinDir.toAbsolutePath().toString()); 356 return false; 357 } 358 359 FileUtils.moveDirectory(tmpDir.toFile(), skinDir.toFile()); 360 return true; 361 } 362 363 /** 364 * Delete a model 365 * @param modelId The model id 366 * @throws IOException if an error occurs when manipulating files 367 */ 368 @Callable 369 public void delete(String modelId) throws IOException 370 { 371 SkinModel model = _modelsManager.getModel(modelId); 372 373 if (!model.isModifiable()) 374 { 375 throw new IllegalStateException("The skin model '" + modelId + "' is not modified and thus cannot be removed."); 376 } 377 378 // Unlink skins 379 Set<String> skins = _skinsManager.getSkins(); 380 for (String skinId : skins) 381 { 382 Skin skin = _skinsManager.getSkin(skinId); 383 if (skin.isModifiable() && modelId.equals(_modelsManager.getModelOfSkin(skin))) 384 { 385 _unlinkModel(skin); 386 } 387 } 388 389 Path file = model.getPath(); 390 if (Files.exists(file)) 391 { 392 PathUtils.deleteDirectory(file); 393 } 394 } 395 396 /** 397 * Unlink the skin from its model 398 * @param skinId The id of the skin 399 * @param modelId The id of the model 400 * @return An error code, or null on success 401 * @throws IOException If an error occurred while removing the link 402 */ 403 @Callable 404 public String unlinkModel(String skinId, String modelId) throws IOException 405 { 406 Skin skin = _skinsManager.getSkin(skinId); 407 408 if (!modelId.equals(_modelsManager.getModelOfSkin(skin))) 409 { 410 return "incorrect-model"; 411 } 412 413 _unlinkModel(skin); 414 415 return null; 416 } 417 418 private void _unlinkModel(Skin skin) throws IOException 419 { 420 if (!skin.isModifiable()) 421 { 422 throw new IllegalStateException("The skin '" + skin.getId() + "' is not modifiable and thus it can not be unlink to its model."); 423 } 424 425 Path skinDir = skin.getRawPath(); 426 427 Path modelFile = skinDir.resolve("model.xml"); 428 Path bakFile = skinDir.resolve("model.xml.bak"); 429 430 // Delete old bak file if exists 431 Files.deleteIfExists(bakFile); 432 433 if (Files.exists(modelFile)) 434 { 435 Files.move(modelFile, bakFile); 436 } 437 } 438}