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