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