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.logger.AbstractLogEnabled;
033import org.apache.avalon.framework.service.ServiceException;
034import org.apache.avalon.framework.service.ServiceManager;
035import org.apache.avalon.framework.service.Serviceable;
036import org.apache.cocoon.ProcessingException;
037import org.apache.commons.io.FileUtils;
038
039import org.ametys.core.ui.Callable;
040import org.ametys.plugins.skincommons.SkinEditionHelper;
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 Set<String> applyModelToAll(String modelId) throws IOException
243    {
244        Set<String> skins = _skinsManager.getSkins();
245        Set<String> modifiedSkins = new HashSet<>();
246        
247        for (String skinId : skins)
248        {
249            Skin skin = _skinsManager.getSkin(skinId);
250            if (modelId.equals(_modelsManager.getModelOfSkin(skin)))
251            {
252                applyModel(skin, modelId);
253                
254                modifiedSkins.add(skinId);
255            }
256        }
257        
258        return modifiedSkins;
259    }
260    
261    /**
262     * Apply model to the skin
263     * @param skinId The skin id
264     * @param modelId The id of model
265     * @throws IOException if an error occurs when manipulating files
266     */
267    @Callable
268    public void applyModel(String skinId, String modelId) throws IOException
269    {
270        Skin skin = _skinsManager.getSkin(skinId);
271        
272        applyModel(skin, modelId);
273    }
274     
275    /**
276     * Apply model to the skin
277     * @param skin The skin
278     * @param modelId The id of model
279     * @throws IOException if an error occurs when manipulating files
280     */
281    protected void applyModel(Skin skin, String modelId) throws IOException
282    {
283        File skinDir = skin.getFile();
284        
285        // Prepare skin in temporary file
286        File tmpDir = new File (skinDir.getParentFile(), skin.getId() + "." + _DATE_FORMAT.format(new Date()));
287        
288        // Copy the model
289        File modelDir = _modelsManager.getModel(modelId).getFile();
290        FileUtils.copyDirectory(modelDir, tmpDir, new ModelFileFilter(modelDir), false);
291        
292        // Copy upload images if exists
293        File uploadDir = new File(skinDir, "model/_uploads");
294        if (uploadDir.exists())
295        {
296            File tmpUploadDir = new File(tmpDir, "model/_uploads");
297            tmpUploadDir.mkdirs();
298            FileUtils.copyDirectory(uploadDir, tmpUploadDir);
299        }
300        
301        // Copy model.xml file
302        File xmlFile = new File(skinDir, "model.xml");
303        FileUtils.copyFileToDirectory(xmlFile, tmpDir);
304        File tmpXmlFile = new File(tmpDir, "model.xml");
305        
306        // Apply parameters
307        _skinFactoryManager.applyModelParameters(modelId, tmpDir);
308        _skinFactoryManager.updateHash(tmpXmlFile, _modelsManager.getModelHash(modelId));
309        
310        _skinHelper.deleteQuicklyDirectory(skinDir);
311        FileUtils.moveDirectory(tmpDir, skinDir);
312    }
313    
314    /**
315     * Delete a model
316     * @param modelId The model id
317     * @throws IOException if an error occurs when manipulating files
318     */
319    @Callable
320    public void delete(String modelId) throws IOException
321    {
322        SkinModel model = _modelsManager.getModel(modelId);
323        
324        // Unlink skins
325        Set<String> skins = _skinsManager.getSkins();
326        for (String skinId : skins)
327        {
328            Skin skin = _skinsManager.getSkin(skinId);
329            if (modelId.equals(_modelsManager.getModelOfSkin(skin)))
330            {
331                unlinkModel(skin);
332            }
333        }
334        
335        File file = model.getFile();
336        if (file.exists())
337        {
338            FileUtils.deleteDirectory(file);
339        }
340    }
341
342    /**
343     * Unlink the skin from its model
344     * @param skinId The id of the skin
345     * @param modelId The id of the model
346     * @return An error code, or null on success
347     */
348    @Callable
349    public String unlinkModel(String skinId, String modelId)
350    {
351        Skin skin = _skinsManager.getSkin(skinId);
352        
353        if (!modelId.equals(_modelsManager.getModelOfSkin(skin)))
354        {
355            return "incorrect-model";
356        }
357        
358        unlinkModel(skin);
359        
360        return null;
361    }
362
363    private void unlinkModel(Skin skin)
364    {
365        File skinDir = skin.getFile();
366        
367        File modelFile = new File (skinDir, "model.xml");
368        File bakFile = new File (skinDir, "model.xml.bak");
369        
370        if (bakFile.exists())
371        {
372            // Delete old bak file if exists
373            bakFile.delete();
374        }
375        
376        if (modelFile.exists())
377        {
378            modelFile.renameTo(bakFile);
379        }
380    }
381}