001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH & Co. KG, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.db;
029
030import org.opencms.file.CmsFile;
031import org.opencms.file.CmsObject;
032import org.opencms.file.CmsProperty;
033import org.opencms.file.CmsPropertyDefinition;
034import org.opencms.file.CmsResource;
035import org.opencms.file.CmsResourceFilter;
036import org.opencms.file.CmsVfsException;
037import org.opencms.file.types.CmsResourceTypeFolder;
038import org.opencms.file.types.CmsResourceTypePlain;
039import org.opencms.main.CmsException;
040import org.opencms.main.CmsLog;
041import org.opencms.main.OpenCms;
042import org.opencms.security.CmsSecurityException;
043import org.opencms.util.CmsFileUtil;
044
045import java.io.ByteArrayInputStream;
046import java.io.File;
047import java.io.FileInputStream;
048import java.io.IOException;
049import java.util.ArrayList;
050import java.util.List;
051import java.util.StringTokenizer;
052import java.util.zip.ZipEntry;
053import java.util.zip.ZipInputStream;
054
055import org.apache.commons.logging.Log;
056
057/**
058 * Allows to import resources from the filesystem or a ZIP file into the OpenCms VFS.<p>
059 *
060 * @since 6.0.0
061 */
062public class CmsImportFolder {
063
064    /** Logger instance for this class. */
065    private static final Log LOG = CmsLog.getLog(CmsImportFolder.class);
066
067    /** The OpenCms context object that provides the permissions. */
068    private CmsObject m_cms;
069
070    /** The names of resources that have been created or replaced during the import. */
071    private List<CmsResource> m_importedResources = new ArrayList<CmsResource>();
072
073    /** The name of the import folder to load resources from. */
074    private String m_importFolderName;
075
076    /** The import path in the OpenCms VFS. */
077    private String m_importPath;
078
079    /** The resource (folder or ZIP file) to import from in the real file system. */
080    private File m_importResource;
081
082    /** Will be true if the import resource is a valid ZIP file. */
083    private boolean m_validZipFile;
084
085    /** The import resource ZIP stream to load resources from. */
086    private ZipInputStream m_zipStreamIn;
087
088    /**
089     * Default Constructor.<p>
090     */
091    public CmsImportFolder() {
092
093        // noop
094    }
095
096    /**
097     * Constructor for a new CmsImportFolder that will read from a ZIP file.<p>
098     *
099     * @param content the zip file to import
100     * @param importPath the path to the OpenCms VFS to import to
101     * @param cms a OpenCms context to provide the permissions
102     * @param noSubFolder if <code>true</code> no sub folders will be created, if <code>false</code> the content of the
103     * zip file is created 1:1 inclusive sub folders
104     *
105     * @throws CmsException if something goes wrong
106     */
107    public CmsImportFolder(byte[] content, String importPath, CmsObject cms, boolean noSubFolder)
108    throws CmsException {
109
110        importZip(content, importPath, cms, noSubFolder);
111    }
112
113    /**
114     * Constructor for a new CmsImportFolder that will read from the real file system.<p>
115     *
116     * @param importFolderName the folder to import
117     * @param importPath the path to the OpenCms VFS to import to
118     * @param cms a OpenCms context to provide the permissions
119     * @throws CmsException if something goes wrong
120     */
121    public CmsImportFolder(String importFolderName, String importPath, CmsObject cms)
122    throws CmsException {
123
124        importFolder(importFolderName, importPath, cms);
125    }
126
127    /**
128     * Returns the list of imported resources.<p>
129     *
130     * @return the list of imported resources
131     */
132    public List<CmsResource> getImportedResources() {
133
134        return m_importedResources;
135    }
136
137    /**
138     * Import that will read from the real file system.<p>
139     *
140     * @param importFolderName the folder to import
141     * @param importPath the path to the OpenCms VFS to import to
142     * @param cms a OpenCms context to provide the permissions
143     * @throws CmsException if something goes wrong
144     */
145    public void importFolder(String importFolderName, String importPath, CmsObject cms) throws CmsException {
146
147        try {
148            m_importedResources = new ArrayList<CmsResource>();
149            m_importFolderName = importFolderName;
150            m_importPath = importPath;
151            m_cms = cms;
152            // open the import resource
153            getImportResource();
154            // first lock the destination path
155            m_cms.lockResource(m_importPath);
156            // import the resources
157            if (m_zipStreamIn == null) {
158                importResources(m_importResource, m_importPath);
159            } else {
160                importZipResource(m_zipStreamIn, m_importPath, false);
161            }
162            // all is done, unlock the resources
163            m_cms.unlockResource(m_importPath);
164        } catch (Exception e) {
165            throw new CmsVfsException(
166                Messages.get().container(Messages.ERR_IMPORT_FOLDER_2, importFolderName, importPath),
167                e);
168        } finally {
169            if (m_zipStreamIn != null) {
170                try {
171                    m_zipStreamIn.close();
172                } catch (Exception e) {
173                    LOG.info(e.getLocalizedMessage(), e);
174                }
175            }
176        }
177
178    }
179
180    /**
181     * Import that will read from a ZIP file.<p>
182     *
183     * @param content the zip file to import
184     * @param importPath the path to the OpenCms VFS to import to
185     * @param cms a OpenCms context to provide the permissions
186     * @param noSubFolder if <code>true</code> no sub folders will be created, if <code>false</code> the content of the
187     * zip file is created 1:1 inclusive sub folders
188     *
189     * @throws CmsException if something goes wrong
190     */
191    public void importZip(byte[] content, String importPath, CmsObject cms, boolean noSubFolder) throws CmsException {
192
193        m_importPath = importPath;
194        m_cms = cms;
195        try {
196            // open the import resource
197            m_zipStreamIn = new ZipInputStream(new ByteArrayInputStream(content));
198            m_cms.readFolder(importPath, CmsResourceFilter.IGNORE_EXPIRATION);
199            // import the resources
200            importZipResource(m_zipStreamIn, m_importPath, noSubFolder);
201        } catch (Exception e) {
202            throw new CmsVfsException(Messages.get().container(Messages.ERR_IMPORT_FOLDER_1, importPath), e);
203        } finally {
204            if (m_zipStreamIn != null) {
205                try {
206                    m_zipStreamIn.close();
207                } catch (Exception e) {
208                    LOG.info(e.getLocalizedMessage());
209                }
210            }
211        }
212
213    }
214
215    /**
216     * Returns true if a valid ZIP file was imported.<p>
217     *
218     * @return true if a valid ZIP file was imported
219     */
220    public boolean isValidZipFile() {
221
222        return m_validZipFile;
223    }
224
225    /**
226     * Stores the import resource in an Object member variable.<p>
227     * @throws CmsVfsException if the file to import is no valid zipfile
228     */
229    private void getImportResource() throws CmsVfsException {
230
231        // get the import resource
232        m_importResource = new File(m_importFolderName);
233        // check if this is a folder or a ZIP file
234        if (m_importResource.isFile()) {
235            try {
236                m_zipStreamIn = new ZipInputStream(new FileInputStream(m_importResource));
237            } catch (IOException e) {
238                // if file but no ZIP file throw an exception
239                throw new CmsVfsException(
240                    Messages.get().container(Messages.ERR_NO_ZIPFILE_1, m_importResource.getName()),
241                    e);
242            }
243        }
244    }
245
246    /**
247     * Imports the resources from the folder in the real file system to the OpenCms VFS.<p>
248     *
249     * @param folder the folder to import from
250     * @param importPath the OpenCms VFS import path to import to
251     * @throws Exception if something goes wrong during file IO
252     */
253    private void importResources(File folder, String importPath) throws Exception {
254
255        String[] diskFiles = folder.list();
256        File currentFile;
257
258        for (int i = 0; i < diskFiles.length; i++) {
259            currentFile = new File(folder, diskFiles[i]);
260
261            if (currentFile.isDirectory()) {
262                // create directory in cms
263                m_importedResources.add(
264                    m_cms.createResource(importPath + currentFile.getName(), CmsResourceTypeFolder.RESOURCE_TYPE_ID));
265                importResources(currentFile, importPath + currentFile.getName() + "/");
266            } else {
267                // import file into cms
268                int type = OpenCms.getResourceManager().getDefaultTypeForName(currentFile.getName()).getTypeId();
269                byte[] content = CmsFileUtil.readFile(currentFile);
270                // create the file
271                try {
272                    m_importedResources.add(
273                        m_cms.createResource(importPath + currentFile.getName(), type, content, null));
274                } catch (CmsSecurityException e) {
275                    // in case of not enough permissions, try to create a plain text file
276                    int plainId = OpenCms.getResourceManager().getResourceType(
277                        CmsResourceTypePlain.getStaticTypeName()).getTypeId();
278                    m_importedResources.add(
279                        m_cms.createResource(importPath + currentFile.getName(), plainId, content, null));
280                }
281                content = null;
282            }
283        }
284    }
285
286    /**
287     * Imports the resources from a ZIP file in the real file system to the OpenCms VFS.<p>
288     *
289     * @param zipStreamIn the input Stream
290     * @param importPath the path in the vfs
291     * @param noSubFolder if <code>true</code> no sub folders will be created, if <code>false</code> the content of the
292     * zip file is created 1:1 inclusive sub folders
293     *
294     * @throws Exception if something goes wrong during file IO
295     */
296    private void importZipResource(ZipInputStream zipStreamIn, String importPath, boolean noSubFolder)
297    throws Exception {
298
299        // HACK: this method looks very crude, it should be re-written sometime...
300
301        boolean isFolder = false;
302        int j, r, stop, size;
303        int entries = 0;
304        byte[] buffer = null;
305        boolean resourceExists;
306
307        while (true) {
308            // handle the single entries ...
309            j = 0;
310            stop = 0;
311            // open the entry ...
312            ZipEntry entry = zipStreamIn.getNextEntry();
313            if (entry == null) {
314                break;
315            }
316            entries++; // count number of entries in zip
317            String actImportPath = importPath;
318            String title = CmsResource.getName(entry.getName());
319            String filename = m_cms.getRequestContext().getFileTranslator().translateResource(entry.getName());
320            // separate path in directories an file name ...
321            StringTokenizer st = new StringTokenizer(filename, "/\\");
322            int count = st.countTokens();
323            String[] path = new String[count];
324
325            if (filename.endsWith("\\") || filename.endsWith("/")) {
326                isFolder = true; // last entry is a folder
327            } else {
328                isFolder = false; // last entry is a file
329            }
330            while (st.hasMoreTokens()) {
331                // store the files and folder names in array ...
332                path[j] = st.nextToken();
333                j++;
334            }
335            stop = isFolder ? path.length : (path.length - 1);
336
337            if (noSubFolder) {
338                stop = 0;
339            }
340            // now write the folders ...
341            for (r = 0; r < stop; r++) {
342                try {
343                    CmsResource createdFolder = m_cms.createResource(
344                        actImportPath + path[r],
345                        CmsResourceTypeFolder.RESOURCE_TYPE_ID);
346                    m_importedResources.add(createdFolder);
347                } catch (CmsException e) {
348                    // of course some folders did already exist!
349                }
350                actImportPath += path[r];
351                actImportPath += "/";
352            }
353            if (!isFolder) {
354                // import file into cms
355                int type = OpenCms.getResourceManager().getDefaultTypeForName(path[path.length - 1]).getTypeId();
356                size = new Long(entry.getSize()).intValue();
357                if (size == -1) {
358                    buffer = CmsFileUtil.readFully(zipStreamIn, false);
359                } else {
360                    buffer = CmsFileUtil.readFully(zipStreamIn, size, false);
361                }
362                filename = actImportPath + path[path.length - 1];
363
364                try {
365                    m_cms.lockResource(filename);
366                    m_cms.readResource(filename);
367                    resourceExists = true;
368                } catch (CmsException e) {
369                    resourceExists = false;
370                }
371
372                int plainId = OpenCms.getResourceManager().getResourceType(
373                    CmsResourceTypePlain.getStaticTypeName()).getTypeId();
374                if (resourceExists) {
375                    CmsResource res = m_cms.readResource(filename, CmsResourceFilter.ALL);
376                    CmsFile file = m_cms.readFile(res);
377                    byte[] contents = file.getContents();
378                    try {
379                        m_cms.replaceResource(filename, res.getTypeId(), buffer, new ArrayList<CmsProperty>(0));
380                        m_importedResources.add(res);
381                    } catch (CmsSecurityException e) {
382                        // in case of not enough permissions, try to create a plain text file
383                        m_cms.replaceResource(filename, plainId, buffer, new ArrayList<CmsProperty>(0));
384                        m_importedResources.add(res);
385                    } catch (CmsDbSqlException sqlExc) {
386                        // SQL error, probably the file is too large for the database settings, restore content
387                        file.setContents(contents);
388                        m_cms.writeFile(file);
389                        throw sqlExc;
390                    }
391                } else {
392                    String newResName = actImportPath + path[path.length - 1];
393                    if (title.lastIndexOf('.') != -1) {
394                        title = title.substring(0, title.lastIndexOf('.'));
395                    }
396                    List<CmsProperty> properties = new ArrayList<CmsProperty>(1);
397                    CmsProperty titleProp = new CmsProperty();
398                    titleProp.setName(CmsPropertyDefinition.PROPERTY_TITLE);
399                    if (OpenCms.getWorkplaceManager().isDefaultPropertiesOnStructure()) {
400                        titleProp.setStructureValue(title);
401                    } else {
402                        titleProp.setResourceValue(title);
403                    }
404                    properties.add(titleProp);
405                    try {
406                        m_importedResources.add(m_cms.createResource(newResName, type, buffer, properties));
407                    } catch (CmsSecurityException e) {
408                        // in case of not enough permissions, try to create a plain text file
409                        m_importedResources.add(m_cms.createResource(newResName, plainId, buffer, properties));
410                    } catch (CmsDbSqlException sqlExc) {
411                        // SQL error, probably the file is too large for the database settings, delete file
412                        m_cms.lockResource(newResName);
413                        m_cms.deleteResource(newResName, CmsResource.DELETE_PRESERVE_SIBLINGS);
414                        throw sqlExc;
415                    }
416                }
417            }
418
419            // close the entry ...
420            zipStreamIn.closeEntry();
421        }
422        zipStreamIn.close();
423        if (entries > 0) {
424            // at least one entry, got a valid zip file ...
425            m_validZipFile = true;
426        }
427    }
428}