001/*
002 *  Copyright 2011 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.actions;
017
018import java.io.File;
019import java.io.FileOutputStream;
020import java.io.IOException;
021import java.io.InputStream;
022import java.util.Enumeration;
023import java.util.HashMap;
024import java.util.LinkedHashMap;
025import java.util.Map;
026
027import org.apache.avalon.framework.context.Context;
028import org.apache.avalon.framework.context.ContextException;
029import org.apache.avalon.framework.context.Contextualizable;
030import org.apache.avalon.framework.parameters.Parameters;
031import org.apache.cocoon.Constants;
032import org.apache.cocoon.acting.ServiceableAction;
033import org.apache.cocoon.environment.ObjectModelHelper;
034import org.apache.cocoon.environment.Redirector;
035import org.apache.cocoon.environment.Request;
036import org.apache.cocoon.environment.SourceResolver;
037import org.apache.cocoon.servlet.multipart.Part;
038import org.apache.cocoon.servlet.multipart.PartOnDisk;
039import org.apache.cocoon.servlet.multipart.RejectedPart;
040import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
041import org.apache.commons.compress.archivers.zip.ZipFile;
042import org.apache.commons.io.FileUtils;
043import org.apache.tika.io.IOUtils;
044
045import org.ametys.core.cocoon.JSonReader;
046import org.ametys.runtime.util.AmetysHomeHelper;
047
048/**
049 * This action receive a form with the "importfile" zip file as an exported skin.
050 * Replace existing skin 
051 */
052public class UploadSkinAction extends ServiceableAction implements Contextualizable
053{
054    /** The cocoon context */
055    protected org.apache.cocoon.environment.Context _cocoonContext;
056
057    @Override
058    public void contextualize(Context context) throws ContextException
059    {
060        _cocoonContext = (org.apache.cocoon.environment.Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT);
061    }
062        
063    @Override
064    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
065    {
066        Request request = ObjectModelHelper.getRequest(objectModel);
067        
068        Map<String, Object> result = new LinkedHashMap<>();
069
070        boolean isModel = parameters.getParameterAsBoolean("model", false);
071        try
072        {
073            // Checking file
074            Part part = (Part) request.get("importfile");
075            if (part instanceof RejectedPart || part == null)
076            {
077                result.put("success", false);
078                result.put("error", "rejected");
079            }
080            else
081            {
082                // Getting file
083                PartOnDisk uploadedFilePart = (PartOnDisk) part;
084                File uploadedFile = uploadedFilePart.getFile();
085                
086                String filename = (String) uploadedFilePart.getHeaders().get("filename");
087                String skinName = filename.substring(0, filename.length() - 4);
088                
089                if (getLogger().isDebugEnabled())
090                {
091                    getLogger().debug("Uploading skin " + skinName);
092                }
093    
094                // Unzipping in tmp
095                File dir = _unzip(uploadedFile);
096                
097                // Filter files
098                _filter(dir, dir.getAbsolutePath(), isModel);
099                
100                // Test if the configuration model file is present.
101                File configFile = new File(dir, "stylesheets" + File.separator + "config" + File.separator + "config-model.xml");
102                
103                result.put("skinDir", dir.getName());
104                result.put("tempDir", dir.getParentFile().getName());
105                result.put("hasConfig", configFile.isFile());
106                result.put("skinName", skinName);
107                result.put("success", "true");
108            }
109        }
110        catch (Exception e)
111        {
112            getLogger().error("Unable to add skin", e);
113            result.put("success", false);
114            
115            Map<String, String> ex = new HashMap<>();
116            ex.put("message", e.getMessage());
117
118            result.put("error", ex);
119        }
120        
121        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
122        
123        return result;    
124    }
125
126    /**
127     * Unzip file in temp directory
128     * @param uploadedFile the uploaded file
129     * @return the unzipped file
130     * @throws IOException if an I/O exception occurs
131     */
132    protected File _unzip(File uploadedFile) throws IOException
133    {
134        File tmpFile = new File(AmetysHomeHelper.getAmetysHomeTmp(), Long.toString(Math.round(Math.random() * 1000000.0)));
135        
136        getLogger().debug("Importing skin in tmp dir " + tmpFile.getAbsolutePath());
137        ZipFile zipFile = new ZipFile(uploadedFile, "cp437");
138        
139        try
140        {
141            Enumeration<ZipArchiveEntry> entries = zipFile.getEntries();
142            while (entries.hasMoreElements())
143            {
144                ZipArchiveEntry zipEntry = entries.nextElement();
145                
146                String zipName = zipEntry.getName();
147    
148                if (zipEntry.isDirectory())
149                {
150                    File newDir = new File(tmpFile, zipName);
151                    newDir.mkdirs();
152                }
153                else
154                {
155                    InputStream is = null;
156                    FileOutputStream fos = null;
157                    
158                    try
159                    {
160                        is = zipFile.getInputStream(zipEntry);
161                        File newFile = new File(tmpFile, zipName);
162                        newFile.getParentFile().mkdirs();
163                        fos = new FileOutputStream(newFile);
164                        
165                        IOUtils.copy(is, fos);
166                    }
167                    finally
168                    {
169                        IOUtils.closeQuietly(is);
170                        IOUtils.closeQuietly(fos);
171                    }
172                }
173            }
174        }
175        finally
176        {
177            ZipFile.closeQuietly(zipFile);
178        }
179        
180        File[] files = tmpFile.listFiles();
181        if (files.length != 1 || !files[0].isDirectory())
182        {
183            throw new IllegalArgumentException("An imported skin or model must be a zip with a single root directory");
184        }
185        
186        return files[0];
187    }
188    
189    /**
190     * Filter skin directory
191     * @param skinDir the skin directory
192     * @param absoluteSkinPath the absolute path of tmp skin directory
193     * @param isModel true if the uploaded skin is the model of skin.
194     * @throws IOException if an error occurred
195     */
196    protected void _filter(File skinDir, String absoluteSkinPath, boolean isModel) throws IOException
197    {
198        File[] files = skinDir.listFiles();
199        for (File file : files)
200        {
201            if (file.isDirectory())
202            {
203                if ("CVS".equals(file.getName()) || ".svn".equals(file.getName()) || (!isModel && "model".equals(file.getName())))
204                {
205                    FileUtils.deleteQuietly(file);
206                }
207                else
208                {
209                    _filter(file, absoluteSkinPath, isModel);
210                }
211            }
212        }
213    }
214}