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