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.File;
046import java.io.IOException;
047import java.util.ArrayList;
048import java.util.List;
049import java.util.StringTokenizer;
050
051import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
052import org.apache.commons.compress.archivers.zip.ZipFile;
053import org.apache.commons.logging.Log;
054
055/**
056 * Allows to import resources from the filesystem or a ZIP file into the OpenCms VFS.<p>
057 *
058 * @since 6.0.0
059 */
060public class CmsImportFolder {
061
062    /** Logger instance for this class. */
063    private static final Log LOG = CmsLog.getLog(CmsImportFolder.class);
064
065    /** The OpenCms context object that provides the permissions. */
066    private CmsObject m_cms;
067
068    /** The names of resources that have been created or replaced during the import. */
069    private List<CmsResource> m_importedResources = new ArrayList<CmsResource>();
070
071    /** The name of the import folder to load resources from. */
072    private String m_importFolderName;
073
074    /** The import path in the OpenCms VFS. */
075    private String m_importPath;
076
077    /** The resource (folder or ZIP file) to import from in the real file system. */
078    private File m_importResource;
079
080    /** Will be true if the import resource is a valid ZIP file. */
081    private boolean m_validZipFile;
082
083    /** The import resource ZIP to load resources from. */
084    private ZipFile m_zipFile;
085
086    /**
087     * Default Constructor.<p>
088     */
089    public CmsImportFolder() {
090
091        // noop
092    }
093
094    /**
095     * Constructor for a new CmsImportFolder that will read from a ZIP file.<p>
096     *
097     * @param content the zip file to import
098     * @param importPath the path to the OpenCms VFS to import to
099     * @param cms a OpenCms context to provide the permissions
100     * @param noSubFolder if <code>true</code> no sub folders will be created, if <code>false</code> the content of the
101     * zip file is created 1:1 inclusive sub folders
102     *
103     * @throws CmsException if something goes wrong
104     */
105    public CmsImportFolder(byte[] content, String importPath, CmsObject cms, boolean noSubFolder)
106    throws CmsException {
107
108        importZip(content, importPath, cms, noSubFolder);
109    }
110
111    /**
112     * Constructor for a new CmsImportFolder that will read from the real file system.<p>
113     *
114     * @param importFolderName the folder to import
115     * @param importPath the path to the OpenCms VFS to import to
116     * @param cms a OpenCms context to provide the permissions
117     * @throws CmsException if something goes wrong
118     */
119    public CmsImportFolder(String importFolderName, String importPath, CmsObject cms)
120    throws CmsException {
121
122        importFolder(importFolderName, importPath, cms);
123    }
124
125    /**
126     * Returns the list of imported resources.<p>
127     *
128     * @return the list of imported resources
129     */
130    public List<CmsResource> getImportedResources() {
131
132        return m_importedResources;
133    }
134
135    /**
136     * Import that will read from the real file system.<p>
137     *
138     * @param importFolderName the folder to import
139     * @param importPath the path to the OpenCms VFS to import to
140     * @param cms a OpenCms context to provide the permissions
141     * @throws CmsException if something goes wrong
142     */
143    public void importFolder(String importFolderName, String importPath, CmsObject cms) throws CmsException {
144
145        try {
146            m_importedResources = new ArrayList<CmsResource>();
147            m_importFolderName = importFolderName;
148            m_importPath = importPath;
149            m_cms = cms;
150            // open the import resource
151            getImportResource();
152            // first lock the destination path
153            m_cms.lockResource(m_importPath);
154            // import the resources
155            if (m_zipFile == null) {
156                importResources(m_importResource, m_importPath);
157            } else {
158                importZipResource(m_zipFile, m_importPath, false);
159            }
160            // all is done, unlock the resources
161            m_cms.unlockResource(m_importPath);
162        } catch (Exception e) {
163            throw new CmsVfsException(
164                Messages.get().container(Messages.ERR_IMPORT_FOLDER_2, importFolderName, importPath),
165                e);
166        } finally {
167            if (m_zipFile != null) {
168                try {
169                    m_zipFile.close();
170                } catch (Exception e) {
171                    LOG.info(e.getLocalizedMessage(), e);
172                }
173            }
174        }
175
176    }
177
178    /**
179     * Import that will read from a ZIP file.<p>
180     *
181     * @param content the zip file to import
182     * @param importPath the path to the OpenCms VFS to import to
183     * @param cms a OpenCms context to provide the permissions
184     * @param noSubFolder if <code>true</code> no sub folders will be created, if <code>false</code> the content of the
185     * zip file is created 1:1 inclusive sub folders
186     *
187     * @throws CmsException if something goes wrong
188     */
189    public void importZip(byte[] content, String importPath, CmsObject cms, boolean noSubFolder) throws CmsException {
190
191        m_importPath = importPath;
192        m_cms = cms;
193        try {
194            m_zipFile = ZipFile.builder().setByteArray(content).setBufferSize(65536).get();
195            m_cms.readFolder(importPath, CmsResourceFilter.IGNORE_EXPIRATION);
196            // import the resources
197            importZipResource(m_zipFile, m_importPath, noSubFolder);
198        } catch (Exception e) {
199            throw new CmsVfsException(Messages.get().container(Messages.ERR_IMPORT_FOLDER_1, importPath), e);
200        } finally {
201            if (m_zipFile != null) {
202                try {
203                    m_zipFile.close();
204                } catch (Exception e) {
205                    LOG.info(e.getLocalizedMessage());
206                }
207            }
208        }
209
210    }
211
212    /**
213     * Returns true if a valid ZIP file was imported.<p>
214     *
215     * @return true if a valid ZIP file was imported
216     */
217    public boolean isValidZipFile() {
218
219        return m_validZipFile;
220    }
221
222    /**
223     * Stores the import resource in an Object member variable.<p>
224     * @throws CmsVfsException if the file to import is no valid zipfile
225     */
226    private void getImportResource() throws CmsVfsException {
227
228        // get the import resource
229        m_importResource = new File(m_importFolderName);
230        // check if this is a folder or a ZIP file
231        if (m_importResource.isFile()) {
232            try {
233                m_zipFile = ZipFile.builder().setFile(m_importResource).setBufferSize(65536).get();
234            } catch (IOException e) {
235                // if file but no ZIP file throw an exception
236                throw new CmsVfsException(
237                    Messages.get().container(Messages.ERR_NO_ZIPFILE_1, m_importResource.getName()),
238                    e);
239            }
240        }
241    }
242
243    /**
244     * Imports the resources from the folder in the real file system to the OpenCms VFS.<p>
245     *
246     * @param folder the folder to import from
247     * @param importPath the OpenCms VFS import path to import to
248     * @throws Exception if something goes wrong during file IO
249     */
250    private void importResources(File folder, String importPath) throws Exception {
251
252        String[] diskFiles = folder.list();
253        File currentFile;
254
255        for (int i = 0; i < diskFiles.length; i++) {
256            currentFile = new File(folder, diskFiles[i]);
257
258            if (currentFile.isDirectory()) {
259                // create directory in cms
260                m_importedResources.add(
261                    m_cms.createResource(importPath + currentFile.getName(), CmsResourceTypeFolder.RESOURCE_TYPE_ID));
262                importResources(currentFile, importPath + currentFile.getName() + "/");
263            } else {
264                // import file into cms
265                int type = OpenCms.getResourceManager().getDefaultTypeForName(currentFile.getName()).getTypeId();
266                byte[] content = CmsFileUtil.readFile(currentFile);
267                // create the file
268                try {
269                    m_importedResources.add(
270                        m_cms.createResource(importPath + currentFile.getName(), type, content, null));
271                } catch (CmsSecurityException e) {
272                    // in case of not enough permissions, try to create a plain text file
273                    int plainId = OpenCms.getResourceManager().getResourceType(
274                        CmsResourceTypePlain.getStaticTypeName()).getTypeId();
275                    m_importedResources.add(
276                        m_cms.createResource(importPath + currentFile.getName(), plainId, content, null));
277                }
278                content = null;
279            }
280        }
281    }
282
283    /**
284     * Imports the resources from a ZIP file in the real file system to the OpenCms VFS.<p>
285     *
286     * @param zipStreamIn the input Stream
287     * @param importPath the path in the vfs
288     * @param noSubFolder if <code>true</code> no sub folders will be created, if <code>false</code> the content of the
289     * zip file is created 1:1 inclusive sub folders
290     *
291     * @throws Exception if something goes wrong during file IO
292     */
293    private void importZipResource(ZipFile zipFile, String importPath, boolean noSubFolder) throws Exception {
294
295        // HACK: this method looks very crude, it should be re-written sometime...
296
297        boolean isFolder = false;
298        int j, r, stop, size;
299        int entries = 0;
300        byte[] buffer = null;
301        boolean resourceExists;
302        for (ZipArchiveEntry entry : (Iterable<ZipArchiveEntry>)() -> zipFile.getEntries().asIterator()) {
303            // handle the single entries ...
304            j = 0;
305            stop = 0;
306            entries++; // count number of entries in zip
307            String actImportPath = importPath;
308            String title = CmsResource.getName(entry.getName());
309            String filename = m_cms.getRequestContext().getFileTranslator().translateResource(entry.getName());
310            // separate path in directories an file name ...
311            StringTokenizer st = new StringTokenizer(filename, "/\\");
312            int count = st.countTokens();
313            String[] path = new String[count];
314
315            if (filename.endsWith("\\") || filename.endsWith("/")) {
316                isFolder = true; // last entry is a folder
317            } else {
318                isFolder = false; // last entry is a file
319            }
320            while (st.hasMoreTokens()) {
321                // store the files and folder names in array ...
322                path[j] = st.nextToken();
323                j++;
324            }
325            stop = isFolder ? path.length : (path.length - 1);
326
327            if (noSubFolder) {
328                stop = 0;
329            }
330            // now write the folders ...
331            for (r = 0; r < stop; r++) {
332                try {
333                    CmsResource createdFolder = m_cms.createResource(
334                        actImportPath + path[r],
335                        CmsResourceTypeFolder.RESOURCE_TYPE_ID);
336                    m_importedResources.add(createdFolder);
337                } catch (CmsException e) {
338                    // of course some folders did already exist!
339                }
340                actImportPath += path[r];
341                actImportPath += "/";
342            }
343            if (!isFolder) {
344                // import file into cms
345                int type = OpenCms.getResourceManager().getDefaultTypeForName(path[path.length - 1]).getTypeId();
346                buffer = CmsFileUtil.readFully(zipFile.getInputStream(entry), true);
347                size = Long.valueOf(entry.getSize()).intValue();
348                filename = actImportPath + path[path.length - 1];
349                try {
350                    m_cms.lockResource(filename);
351                    m_cms.readResource(filename);
352                    resourceExists = true;
353                } catch (CmsException e) {
354                    resourceExists = false;
355                }
356
357                int plainId = OpenCms.getResourceManager().getResourceType(
358                    CmsResourceTypePlain.getStaticTypeName()).getTypeId();
359                if (resourceExists) {
360                    CmsResource res = m_cms.readResource(filename, CmsResourceFilter.ALL);
361                    CmsFile file = m_cms.readFile(res);
362                    byte[] contents = file.getContents();
363                    try {
364                        m_cms.replaceResource(filename, res.getTypeId(), buffer, new ArrayList<CmsProperty>(0));
365                        m_importedResources.add(res);
366                    } catch (CmsSecurityException e) {
367                        // in case of not enough permissions, try to create a plain text file
368                        m_cms.replaceResource(filename, plainId, buffer, new ArrayList<CmsProperty>(0));
369                        m_importedResources.add(res);
370                    } catch (CmsDbSqlException sqlExc) {
371                        // SQL error, probably the file is too large for the database settings, restore content
372                        file.setContents(contents);
373                        m_cms.writeFile(file);
374                        throw sqlExc;
375                    }
376                } else {
377                    String newResName = actImportPath + path[path.length - 1];
378                    if (title.lastIndexOf('.') != -1) {
379                        title = title.substring(0, title.lastIndexOf('.'));
380                    }
381                    List<CmsProperty> properties = new ArrayList<CmsProperty>(1);
382                    CmsProperty titleProp = new CmsProperty();
383                    titleProp.setName(CmsPropertyDefinition.PROPERTY_TITLE);
384                    if (OpenCms.getWorkplaceManager().isDefaultPropertiesOnStructure()) {
385                        titleProp.setStructureValue(title);
386                    } else {
387                        titleProp.setResourceValue(title);
388                    }
389                    properties.add(titleProp);
390                    try {
391                        m_importedResources.add(m_cms.createResource(newResName, type, buffer, properties));
392                    } catch (CmsSecurityException e) {
393                        // in case of not enough permissions, try to create a plain text file
394                        m_importedResources.add(m_cms.createResource(newResName, plainId, buffer, properties));
395                    } catch (CmsDbSqlException sqlExc) {
396                        // SQL error, probably the file is too large for the database settings, delete file
397                        m_cms.lockResource(newResName);
398                        m_cms.deleteResource(newResName, CmsResource.DELETE_PRESERVE_SIBLINGS);
399                        throw sqlExc;
400                    }
401                }
402            }
403        }
404        if (entries > 0) {
405            // at least one entry, got a valid zip file ...
406            m_validZipFile = true;
407        }
408    }
409}