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.importexport;
029
030import org.opencms.db.CmsDefaultUsers;
031import org.opencms.db.CmsResourceState;
032import org.opencms.file.CmsFile;
033import org.opencms.file.CmsFolder;
034import org.opencms.file.CmsGroup;
035import org.opencms.file.CmsObject;
036import org.opencms.file.CmsProject;
037import org.opencms.file.CmsProperty;
038import org.opencms.file.CmsPropertyDefinition;
039import org.opencms.file.CmsResource;
040import org.opencms.file.CmsResourceFilter;
041import org.opencms.file.CmsUser;
042import org.opencms.file.CmsVfsException;
043import org.opencms.file.CmsVfsResourceNotFoundException;
044import org.opencms.file.types.CmsResourceTypePlain;
045import org.opencms.i18n.CmsMessageContainer;
046import org.opencms.importexport.CmsImportExportManager.TimestampMode;
047import org.opencms.main.CmsEvent;
048import org.opencms.main.CmsException;
049import org.opencms.main.CmsLog;
050import org.opencms.main.I_CmsEventListener;
051import org.opencms.main.OpenCms;
052import org.opencms.module.CmsModule.ExportMode;
053import org.opencms.relations.CmsRelation;
054import org.opencms.relations.CmsRelationFilter;
055import org.opencms.report.I_CmsReport;
056import org.opencms.security.CmsAccessControlEntry;
057import org.opencms.security.CmsOrganizationalUnit;
058import org.opencms.security.CmsRole;
059import org.opencms.security.CmsRoleViolationException;
060import org.opencms.util.CmsDataTypeUtil;
061import org.opencms.util.CmsDateUtil;
062import org.opencms.util.CmsFileUtil;
063import org.opencms.util.CmsMacroResolver;
064import org.opencms.util.CmsStringUtil;
065import org.opencms.util.CmsUUID;
066import org.opencms.util.CmsXmlSaxWriter;
067import org.opencms.workplace.CmsWorkplace;
068
069import java.io.IOException;
070import java.util.ArrayList;
071import java.util.Collections;
072import java.util.HashMap;
073import java.util.HashSet;
074import java.util.Iterator;
075import java.util.List;
076import java.util.Map;
077import java.util.Set;
078import java.util.stream.Collectors;
079
080import org.apache.commons.codec.binary.Base64;
081import org.apache.commons.logging.Log;
082
083import org.dom4j.Document;
084import org.dom4j.DocumentHelper;
085import org.dom4j.Element;
086import org.dom4j.io.SAXWriter;
087import org.xml.sax.SAXException;
088
089/**
090 * Provides the functionality to export files from the OpenCms VFS to a ZIP file.<p>
091 *
092 * The ZIP file written will contain a copy of all exported files with their contents.
093 * It will also contain a <code>manifest.xml</code> file in which all meta-information
094 * about this files are stored, like permissions etc.<p>
095 *
096 * @since 6.0.0
097 */
098public class CmsExport {
099
100    /** The log object for this class. */
101    private static final Log LOG = CmsLog.getLog(CmsExport.class);
102
103    /** The cms context. */
104    private CmsObject m_cms;
105
106    /** Counter for the export. */
107    private int m_exportCount;
108
109    /** Set of all exported files, required for preventing redundant sibling export. */
110    private Set<CmsUUID> m_exportedResources;
111
112    /** The export writer. */
113    private CmsExportHelper m_exportWriter;
114
115    /** The export parameters. */
116    private CmsExportParameters m_parameters;
117
118    /** The report. */
119    private I_CmsReport m_report;
120
121    /** The top level file node where all resources are appended to. */
122    private Element m_resourceNode;
123
124    /** The SAX writer to write the output to. */
125    private SAXWriter m_saxWriter;
126
127    /** Cache for previously added super folders. */
128    private List<String> m_superFolders;
129
130    /**
131     * Constructs a new uninitialized export, required for special subclass data export.<p>
132     */
133    public CmsExport() {
134
135        // empty constructor
136    }
137
138    /**
139     * Constructs a new export.<p>
140     *
141     * @param cms the cms context
142     * @param report the report
143     *
144     * @throws CmsRoleViolationException if the current user has not the required role
145     */
146    public CmsExport(CmsObject cms, I_CmsReport report)
147    throws CmsRoleViolationException {
148
149        m_cms = cms;
150        m_report = report;
151
152        // check if the user has the required permissions
153        OpenCms.getRoleManager().checkRole(getCms(), CmsRole.DATABASE_MANAGER);
154
155    }
156
157    /**
158     * Export the data.<p>
159     *
160     * @param parameters the export parameters
161     *
162     * @throws CmsImportExportException if something goes wrong
163     */
164    public void exportData(CmsExportParameters parameters) throws CmsImportExportException {
165
166        m_parameters = parameters;
167        m_exportCount = 0;
168
169        // clear all caches
170        getReport().println(Messages.get().container(Messages.RPT_CLEARCACHE_0), I_CmsReport.FORMAT_NOTE);
171        OpenCms.fireCmsEvent(new CmsEvent(I_CmsEventListener.EVENT_CLEAR_CACHES, new HashMap<String, Object>(0)));
172
173        try {
174            Element exportNode = openExportFile(parameters.getExportMode());
175
176            if (m_parameters.getModuleInfo() != null) {
177                // add the module element
178                exportNode.add(m_parameters.getModuleInfo());
179                // write the XML
180                digestElement(exportNode, m_parameters.getModuleInfo());
181            }
182
183            // export account data only if selected
184            if (m_parameters.isExportAccountData()) {
185                Element accountsElement = exportNode.addElement(CmsImportVersion10.N_ACCOUNTS);
186                getSaxWriter().writeOpen(accountsElement);
187
188                exportOrgUnits(accountsElement);
189
190                getSaxWriter().writeClose(accountsElement);
191                exportNode.remove(accountsElement);
192            }
193
194            // export resource data only if selected
195            if (m_parameters.isExportResourceData()) {
196                m_parameters.addAdditionalResources();
197                exportAllResources(exportNode, m_parameters.getResources());
198            }
199
200            // export project data only if selected
201            if (m_parameters.isExportProjectData()) {
202                Element projectsElement = exportNode.addElement(CmsImportVersion10.N_PROJECTS);
203                getSaxWriter().writeOpen(projectsElement);
204
205                exportProjects(projectsElement);
206
207                getSaxWriter().writeClose(projectsElement);
208                exportNode.remove(projectsElement);
209            }
210
211            closeExportFile(exportNode);
212        } catch (SAXException se) {
213            getReport().println(se);
214
215            CmsMessageContainer message = Messages.get().container(
216                Messages.ERR_IMPORTEXPORT_ERROR_EXPORTING_TO_FILE_1,
217                getExportFileName());
218            if (LOG.isDebugEnabled()) {
219                LOG.debug(message.key(), se);
220            }
221
222            throw new CmsImportExportException(message, se);
223        } catch (IOException ioe) {
224            getReport().println(ioe);
225
226            CmsMessageContainer message = Messages.get().container(
227                Messages.ERR_IMPORTEXPORT_ERROR_EXPORTING_TO_FILE_1,
228                getExportFileName());
229            if (LOG.isDebugEnabled()) {
230                LOG.debug(message.key(), ioe);
231            }
232
233            throw new CmsImportExportException(message, ioe);
234        } finally {
235            if (m_exportWriter != null) {
236                m_exportWriter.ensureZipStreamClosed();
237            }
238        }
239    }
240
241    /**
242     * Exports the given folder and all child resources.<p>
243     *
244     * @param folderName to complete path to the resource to export
245     *
246     * @throws CmsImportExportException if something goes wrong
247     * @throws SAXException if something goes wrong processing the manifest.xml
248     * @throws IOException if not all resources could be appended to the ZIP archive
249     */
250    protected void addChildResources(String folderName) throws CmsImportExportException, IOException, SAXException {
251
252        try {
253            // get all subFolders
254            List<CmsResource> subFolders = getCms().getSubFolders(folderName, CmsResourceFilter.IGNORE_EXPIRATION);
255            // get all files in folder
256            List<CmsResource> subFiles = getCms().getFilesInFolder(folderName, CmsResourceFilter.IGNORE_EXPIRATION);
257
258            // walk through all files and export them
259            for (int i = 0; i < subFiles.size(); i++) {
260                CmsResource file = subFiles.get(i);
261                CmsResourceState state = file.getState();
262                long age = file.getDateLastModified() < file.getDateCreated()
263                ? file.getDateCreated()
264                : file.getDateLastModified();
265
266                if (getCms().getRequestContext().getCurrentProject().isOnlineProject()
267                    || (m_parameters.isIncludeUnchangedResources())
268                    || state.isNew()
269                    || state.isChanged()) {
270                    if (!state.isDeleted()
271                        && !CmsWorkplace.isTemporaryFile(file)
272                        && (age >= m_parameters.getContentAge())) {
273                        String export = getCms().getSitePath(file);
274                        if (checkExportResource(export)) {
275                            if (isInExportableProject(file)) {
276                                exportFile(getCms().readFile(export, CmsResourceFilter.IGNORE_EXPIRATION));
277                            }
278                        }
279                    }
280                }
281                // release file header memory
282                subFiles.set(i, null);
283            }
284            // all files are exported, release memory
285            subFiles = null;
286
287            // walk through all subfolders and export them
288            for (int i = 0; i < subFolders.size(); i++) {
289                CmsResource folder = subFolders.get(i);
290                if (folder.getState() != CmsResource.STATE_DELETED) {
291                    // check if this is a system-folder and if it should be included.
292                    String export = getCms().getSitePath(folder);
293                    if (checkExportResource(export)) {
294
295                        long age = folder.getDateLastModified() < folder.getDateCreated()
296                        ? folder.getDateCreated()
297                        : folder.getDateLastModified();
298                        // export this folder only if age is above selected age
299                        // default for selected age (if not set by user) is <code>long 0</code> (i.e. 1970)
300                        if (age >= m_parameters.getContentAge()) {
301                            // only export folder data to manifest.xml if it has changed
302                            appendResourceToManifest(folder, false);
303                        }
304
305                        // export all sub-resources in this folder
306                        addChildResources(getCms().getSitePath(folder));
307                    }
308                }
309                // release folder memory
310                subFolders.set(i, null);
311            }
312        } catch (CmsImportExportException e) {
313
314            throw e;
315        } catch (CmsException e) {
316
317            CmsMessageContainer message = Messages.get().container(
318                Messages.ERR_IMPORTEXPORT_ERROR_ADDING_CHILD_RESOURCES_1,
319                folderName);
320            if (LOG.isDebugEnabled()) {
321                LOG.debug(message.key(), e);
322            }
323
324            throw new CmsImportExportException(message, e);
325        }
326    }
327
328    /**
329     * Adds all files in fileNames to the manifest.xml file.<p>
330     *
331     * @param fileNames list of path Strings, e.g. <code>/folder/index.html</code>
332     *
333     * @throws CmsImportExportException if something goes wrong
334     * @throws IOException if a file could not be exported
335     * @throws SAXException if something goes wrong processing the manifest.xml
336     */
337    protected void addFiles(List<String> fileNames) throws CmsImportExportException, IOException, SAXException {
338
339        if (fileNames != null) {
340            for (int i = 0; i < fileNames.size(); i++) {
341                String fileName = fileNames.get(i);
342
343                try {
344                    CmsFile file = getCms().readFile(fileName, CmsResourceFilter.IGNORE_EXPIRATION);
345                    if (!file.getState().isDeleted() && !CmsWorkplace.isTemporaryFile(file)) {
346                        if (checkExportResource(fileName)) {
347                            if (m_parameters.isRecursive()) {
348                                addParentFolders(fileName);
349                            }
350                            if (isInExportableProject(file)) {
351                                exportFile(file);
352                            }
353                        }
354                    }
355                } catch (CmsImportExportException e) {
356
357                    throw e;
358                } catch (CmsException e) {
359                    if (e instanceof CmsVfsException) { // file not found
360                        CmsMessageContainer message = Messages.get().container(
361                            Messages.ERR_IMPORTEXPORT_ERROR_ADDING_FILE_1,
362                            fileName);
363                        if (LOG.isDebugEnabled()) {
364                            LOG.debug(message.key(), e);
365                        }
366
367                        throw new CmsImportExportException(message, e);
368                    }
369                }
370            }
371        }
372    }
373
374    /**
375     * Adds the parent folders of the given resource to the config file,
376     * starting at the top, excluding the root folder.<p>
377     *
378     * @param resourceName the name of a resource in the VFS
379     *
380     * @throws CmsImportExportException if something goes wrong
381     * @throws SAXException if something goes wrong processing the manifest.xml
382     */
383    protected void addParentFolders(String resourceName) throws CmsImportExportException, SAXException {
384
385        try {
386            // this is a resource in /system/ folder and option includeSystem is not true
387            if (!checkExportResource(resourceName)) {
388                return;
389            }
390
391            // Initialize the "previously added folder cache"
392            if (m_superFolders == null) {
393                m_superFolders = new ArrayList<String>();
394            }
395            List<String> superFolders = new ArrayList<String>();
396            String currentSubFolder = resourceName;
397
398            // Check, if the path is really a folder
399            boolean isFolderResource = currentSubFolder.endsWith("/");
400
401            while (currentSubFolder.length() > "/".length()) {
402                currentSubFolder = currentSubFolder.substring(0, currentSubFolder.length() - 1);
403                currentSubFolder = currentSubFolder.substring(0, currentSubFolder.lastIndexOf("/") + 1);
404                if (currentSubFolder.length() <= "/".length()) {
405                    break;
406                }
407                superFolders.add(currentSubFolder);
408            }
409            for (int i = superFolders.size() - 1; i >= 0; i--) {
410                String addFolder = superFolders.get(i);
411                if (!m_superFolders.contains(addFolder)) {
412                    // This super folder was NOT added previously. Add it now!
413                    CmsFolder folder = getCms().readFolder(addFolder, CmsResourceFilter.IGNORE_EXPIRATION);
414                    appendResourceToManifest(folder, false, true);
415                    // Remember that this folder was added
416                    m_superFolders.add(addFolder);
417                }
418            }
419            if (isFolderResource) { // add the folder itself
420                if (!m_superFolders.contains(resourceName)) {
421                    // This super folder was NOT added previously. Add it now!
422                    CmsFolder folder = getCms().readFolder(resourceName, CmsResourceFilter.IGNORE_EXPIRATION);
423                    appendResourceToManifest(folder, false);
424                    // Remember that this folder was added
425                    m_superFolders.add(resourceName);
426                }
427            }
428        } catch (CmsImportExportException e) {
429
430            throw e;
431        } catch (CmsException e) {
432
433            CmsMessageContainer message = Messages.get().container(
434                Messages.ERR_IMPORTEXPORT_ERROR_ADDING_PARENT_FOLDERS_1,
435                resourceName);
436            if (LOG.isDebugEnabled()) {
437                LOG.debug(message.key(), e);
438            }
439
440            throw new CmsImportExportException(message, e);
441        }
442    }
443
444    /**
445     * Adds a property node to the manifest.xml.<p>
446     *
447     * @param propertiesElement the parent element to append the node to
448     * @param propertyName the name of the property
449     * @param propertyValue the value of the property
450     * @param shared if <code>true</code>, add a shared property attribute to the generated property node
451     */
452    protected void addPropertyNode(
453        Element propertiesElement,
454        String propertyName,
455        String propertyValue,
456        boolean shared) {
457
458        if (propertyValue != null) {
459            Element propertyElement = propertiesElement.addElement(CmsImportVersion10.N_PROPERTY);
460            if (shared) {
461                // add "type" attribute to the property node in case of a shared/resource property value
462                propertyElement.addAttribute(CmsImportVersion10.A_TYPE, CmsImportVersion10.PROPERTY_ATTRIB_TYPE_SHARED);
463            }
464            propertyElement.addElement(CmsImportVersion10.N_NAME).addText(propertyName);
465            propertyElement.addElement(CmsImportVersion10.N_VALUE).addCDATA(propertyValue);
466        }
467    }
468
469    /**
470     * Adds a relation node to the <code>manifest.xml</code>.<p>
471     *
472     * @param relationsElement the parent element to append the node to
473     * @param structureId the structure id of the target relation
474     * @param sitePath the site path of the target relation
475     * @param relationType the type of the relation
476     */
477    protected void addRelationNode(Element relationsElement, String structureId, String sitePath, String relationType) {
478
479        if ((sitePath != null) && (relationType != null)) {
480            Element relationElement = relationsElement.addElement(CmsImportVersion10.N_RELATION);
481            if (structureId != null) {
482                relationElement.addElement(CmsImportVersion10.N_ID).addText(structureId);
483            }
484            relationElement.addElement(CmsImportVersion10.N_PATH).addText(sitePath);
485            relationElement.addElement(CmsImportVersion10.N_TYPE).addText(relationType);
486        }
487    }
488
489    /** @see #appendResourceToManifest(CmsResource, boolean, boolean)
490     * @param resource @see #appendResourceToManifest(CmsResource, boolean, boolean)
491     * @param source @see #appendResourceToManifest(CmsResource, boolean, boolean)
492     * @throws CmsImportExportException @see #appendResourceToManifest(CmsResource, boolean, boolean)
493     * @throws SAXException @see #appendResourceToManifest(CmsResource, boolean, boolean)
494     */
495    protected void appendResourceToManifest(CmsResource resource, boolean source)
496    throws CmsImportExportException, SAXException {
497
498        appendResourceToManifest(resource, source, false);
499    }
500
501    /**
502     * Writes the data for a resource (like access-rights) to the <code>manifest.xml</code> file.<p>
503     *
504     * @param resource the resource to get the data from
505     * @param source flag to show if the source information in the xml file must be written
506     * @param isSuperFolder flag to indicate that the resource is only a super folder of a module resource.
507     *  This will prevent exporting uuid and creation date in the reduced export mode.
508     *
509     * @throws CmsImportExportException if something goes wrong
510     * @throws SAXException if something goes wrong processing the manifest.xml
511     */
512    protected void appendResourceToManifest(CmsResource resource, boolean source, boolean isSuperFolder)
513    throws CmsImportExportException, SAXException {
514
515        if (isSuperFolder && m_parameters.isSkipParentFolders()) {
516            return;
517        }
518        try {
519            // only write <source> if resource is a file
520            String fileName = trimResourceName(getCms().getSitePath(resource));
521            if (fileName.startsWith("system/orgunits")) {
522                // it is not allowed to export organizational unit resources
523                // export the organizational units instead
524                return;
525            }
526
527            // define the file node
528            Element fileElement = m_resourceNode.addElement(CmsImportVersion10.N_FILE);
529
530            if (resource.isFile()) {
531                if (source) {
532                    fileElement.addElement(CmsImportVersion10.N_SOURCE).addText(fileName);
533                }
534            } else {
535                m_exportCount++;
536                I_CmsReport report = getReport();
537                // output something to the report for the folder
538                report.print(
539                    org.opencms.report.Messages.get().container(
540                        org.opencms.report.Messages.RPT_SUCCESSION_1,
541                        String.valueOf(m_exportCount)),
542                    I_CmsReport.FORMAT_NOTE);
543                report.print(Messages.get().container(Messages.RPT_EXPORT_0), I_CmsReport.FORMAT_NOTE);
544                report.print(
545                    org.opencms.report.Messages.get().container(
546                        org.opencms.report.Messages.RPT_ARGUMENT_1,
547                        getCms().getSitePath(resource)));
548                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
549                report.println(
550                    org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
551                    I_CmsReport.FORMAT_OK);
552
553                if (LOG.isInfoEnabled()) {
554                    LOG.info(
555                        Messages.get().getBundle().key(
556                            Messages.LOG_EXPORTING_OK_2,
557                            String.valueOf(m_exportCount),
558                            getCms().getSitePath(resource)));
559                }
560            }
561
562            boolean isReducedExportMode = m_parameters.getExportMode().equals(ExportMode.REDUCED);
563            boolean isMinimalMetaData = isReducedExportMode && isSuperFolder && exportWithMinimalMetaData(fileName);
564
565            // <destination>
566            fileElement.addElement(CmsImportVersion10.N_DESTINATION).addText(fileName);
567            // <type>
568            Element typeElem = fileElement.addElement(CmsImportVersion10.N_TYPE);
569            String typeName = null;
570            try {
571                typeName = OpenCms.getResourceManager().getResourceType(resource.getTypeId()).getTypeName();
572            } catch (CmsException e) {
573                LOG.warn(e.getLocalizedMessage(), e);
574                typeName = "unknown_" + resource.getTypeId();
575            }
576            typeElem.addText(typeName);
577
578            if (!isMinimalMetaData) {
579                //  <uuidstructure>
580                fileElement.addElement(CmsImportVersion10.N_UUIDSTRUCTURE).addText(
581                    resource.getStructureId().toString());
582                if (resource.isFile()) {
583                    //  <uuidresource>
584                    fileElement.addElement(CmsImportVersion10.N_UUIDRESOURCE).addText(
585                        resource.getResourceId().toString());
586                }
587            }
588
589            if (!isReducedExportMode) {
590                // <datelastmodified>
591                fileElement.addElement(CmsImportVersion10.N_DATELASTMODIFIED).addText(
592                    getDateLastModifiedForExport(resource));
593                // <userlastmodified>
594                String userNameLastModified = null;
595                try {
596                    userNameLastModified = getCms().readUser(resource.getUserLastModified()).getName();
597                } catch (@SuppressWarnings("unused") CmsException e) {
598                    userNameLastModified = OpenCms.getDefaultUsers().getUserAdmin();
599                }
600                fileElement.addElement(CmsImportVersion10.N_USERLASTMODIFIED).addText(userNameLastModified);
601            }
602            if (!isMinimalMetaData) {
603                // <datecreated>
604                fileElement.addElement(CmsImportVersion10.N_DATECREATED).addText(
605                    CmsDateUtil.getHeaderDate(resource.getDateCreated()));
606            }
607            if (!isReducedExportMode) {
608                // <usercreated>
609                String userNameCreated = null;
610                try {
611                    userNameCreated = getCms().readUser(resource.getUserCreated()).getName();
612                } catch (@SuppressWarnings("unused") CmsException e) {
613                    userNameCreated = OpenCms.getDefaultUsers().getUserAdmin();
614                }
615                fileElement.addElement(CmsImportVersion10.N_USERCREATED).addText(userNameCreated);
616            }
617            if (!isMinimalMetaData) {
618                // <release>
619                if (resource.getDateReleased() != CmsResource.DATE_RELEASED_DEFAULT) {
620                    fileElement.addElement(CmsImportVersion10.N_DATERELEASED).addText(
621                        CmsDateUtil.getHeaderDate(resource.getDateReleased()));
622                }
623                // <expire>
624                if (resource.getDateExpired() != CmsResource.DATE_EXPIRED_DEFAULT) {
625                    fileElement.addElement(CmsImportVersion10.N_DATEEXPIRED).addText(
626                        CmsDateUtil.getHeaderDate(resource.getDateExpired()));
627                }
628                // <flags>
629                int resFlags = resource.getFlags();
630                resFlags &= ~CmsResource.FLAG_LABELED;
631                fileElement.addElement(CmsImportVersion10.N_FLAGS).addText(Integer.toString(resFlags));
632
633                // write the properties to the manifest
634                Element propertiesElement = fileElement.addElement(CmsImportVersion10.N_PROPERTIES);
635                List<CmsProperty> properties = getCms().readPropertyObjects(getCms().getSitePath(resource), false);
636                CmsProperty exportTypeProp = CmsProperty.get(CmsPropertyDefinition.PROPERTY_EXPORT_TYPE, properties);
637                String exportType = exportTypeProp.getValue();
638                if ((exportType != null) && CmsResourceTypePlain.getStaticTypeName().equals(typeElem.getText())) {
639                    typeElem.setText(exportType);
640                }
641                // sort the properties for a well defined output order
642                Collections.sort(properties);
643                for (int i = 0, n = properties.size(); i < n; i++) {
644                    CmsProperty property = properties.get(i);
645                    if (isIgnoredProperty(property)) {
646                        continue;
647                    }
648                    addPropertyNode(propertiesElement, property.getName(), property.getStructureValue(), false);
649                    addPropertyNode(propertiesElement, property.getName(), property.getResourceValue(), true);
650                }
651
652                // Write the relations to the manifest
653                List<CmsRelation> relations = getCms().getRelationsForResource(
654                    resource,
655                    CmsRelationFilter.TARGETS.filterNotDefinedInContent());
656                Element relationsElement = fileElement.addElement(CmsImportVersion10.N_RELATIONS);
657                // iterate over the relations
658                for (CmsRelation relation : relations) {
659                    // relation may be broken already:
660                    try {
661                        CmsResource target = relation.getTarget(getCms(), CmsResourceFilter.ALL);
662                        String structureId = target.getStructureId().toString();
663                        String sitePath = getCms().getSitePath(target);
664                        String relationType = relation.getType().getName();
665                        if (OpenCms.getImportExportManager().isImmutable(target.getRootPath())) {
666                            structureId = null;
667                        }
668                        addRelationNode(relationsElement, structureId, sitePath, relationType);
669                    } catch (CmsVfsResourceNotFoundException crnfe) {
670                        // skip this relation:
671                        if (LOG.isWarnEnabled()) {
672                            LOG.warn(
673                                Messages.get().getBundle().key(
674                                    Messages.LOG_IMPORTEXPORT_WARN_DELETED_RELATIONS_2,
675                                    new String[] {relation.getTargetPath(), resource.getRootPath()}),
676                                crnfe);
677                        }
678                    }
679                }
680
681                // append the nodes for access control entries
682                Element acl = fileElement.addElement(CmsImportVersion10.N_ACCESSCONTROL_ENTRIES);
683
684                // read the access control entries
685                List<CmsAccessControlEntry> fileAcEntries = getCms().getAccessControlEntries(
686                    getCms().getSitePath(resource),
687                    false);
688                Iterator<CmsAccessControlEntry> i = fileAcEntries.iterator();
689
690                // create xml elements for each access control entry
691                while (i.hasNext()) {
692                    CmsAccessControlEntry ace = i.next();
693                    Element a = acl.addElement(CmsImportVersion10.N_ACCESSCONTROL_ENTRY);
694
695                    // now check if the principal is a group or a user
696                    int flags = ace.getFlags();
697                    String acePrincipalName = "";
698                    CmsUUID acePrincipal = ace.getPrincipal();
699                    if ((flags & CmsAccessControlEntry.ACCESS_FLAGS_ALLOTHERS) > 0) {
700                        acePrincipalName = CmsAccessControlEntry.PRINCIPAL_ALL_OTHERS_NAME;
701                    } else if ((flags & CmsAccessControlEntry.ACCESS_FLAGS_OVERWRITE_ALL) > 0) {
702                        acePrincipalName = CmsAccessControlEntry.PRINCIPAL_OVERWRITE_ALL_NAME;
703                    } else if ((flags & CmsAccessControlEntry.ACCESS_FLAGS_GROUP) > 0) {
704                        // the principal is a group
705                        try {
706                            acePrincipalName = getCms().readGroup(acePrincipal).getPrefixedName();
707                        } catch (@SuppressWarnings("unused") CmsException e) {
708                            // the group for this permissions does not exist anymore, so simply skip it
709                        }
710                    } else if ((flags & CmsAccessControlEntry.ACCESS_FLAGS_USER) > 0) {
711                        // the principal is a user
712                        try {
713                            acePrincipalName = getCms().readUser(acePrincipal).getPrefixedName();
714                        } catch (@SuppressWarnings("unused") CmsException e) {
715                            // the user for this permissions does not exist anymore, so simply skip it
716                        }
717                    } else {
718                        // the principal is a role
719                        acePrincipalName = CmsRole.PRINCIPAL_ROLE + "." + CmsRole.valueOfId(acePrincipal).getRoleName();
720                    }
721
722                    // only add the permission if a principal was set
723                    if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(acePrincipalName)) {
724                        a.addElement(CmsImportVersion10.N_ACCESSCONTROL_PRINCIPAL).addText(acePrincipalName);
725                        a.addElement(CmsImportVersion10.N_FLAGS).addText(Integer.toString(flags));
726
727                        Element b = a.addElement(CmsImportVersion10.N_ACCESSCONTROL_PERMISSIONSET);
728                        b.addElement(CmsImportVersion10.N_ACCESSCONTROL_ALLOWEDPERMISSIONS).addText(
729                            Integer.toString(ace.getAllowedPermissions()));
730                        b.addElement(CmsImportVersion10.N_ACCESSCONTROL_DENIEDPERMISSIONS).addText(
731                            Integer.toString(ace.getDeniedPermissions()));
732                    }
733                }
734            } else {
735                fileElement.addElement(CmsImportVersion10.N_PROPERTIES);
736            }
737
738            // write the XML
739            digestElement(m_resourceNode, fileElement);
740        } catch (CmsImportExportException e) {
741
742            throw e;
743        } catch (CmsException e) {
744
745            CmsMessageContainer message = Messages.get().container(
746                Messages.ERR_IMPORTEXPORT_ERROR_APPENDING_RESOURCE_TO_MANIFEST_1,
747                resource.getRootPath());
748            if (LOG.isDebugEnabled()) {
749                LOG.debug(message.key(), e);
750            }
751
752            throw new CmsImportExportException(message, e);
753        }
754    }
755
756    /**
757     * Returns true if the checked resource name can be exported depending on the include settings.<p>
758     *
759     * @param resourcename the absolute path of the resource
760     * @return true if the checked resource name can be exported depending on the include settings
761     */
762    protected boolean checkExportResource(String resourcename) {
763
764        return (// other folder than "/system/" will be exported
765        !resourcename.startsWith(CmsWorkplace.VFS_PATH_SYSTEM) // OR always export "/system/"
766            || resourcename.equalsIgnoreCase(CmsWorkplace.VFS_PATH_SYSTEM) // OR always export "/system/galleries/"
767            || resourcename.startsWith(CmsWorkplace.VFS_PATH_GALLERIES) // OR option "include system folder" selected
768            || (m_parameters.isIncludeSystemFolder() // AND export folder is a system folder
769                && resourcename.startsWith(CmsWorkplace.VFS_PATH_SYSTEM)));
770    }
771
772    /**
773     * Closes the export ZIP file and saves the XML document for the manifest.<p>
774     *
775     * @param exportNode the export root node
776     *
777     * @throws SAXException if something goes wrong processing the manifest.xml
778     * @throws IOException if something goes wrong while closing the export file
779     */
780    protected void closeExportFile(Element exportNode) throws IOException, SAXException {
781
782        // close the <export> Tag
783        getSaxWriter().writeClose(exportNode);
784
785        // close the XML document
786        CmsXmlSaxWriter xmlSaxWriter = (CmsXmlSaxWriter)getSaxWriter().getContentHandler();
787
788        // write the manifest file
789        m_exportWriter.writeManifest(xmlSaxWriter);
790    }
791
792    /**
793     * Writes the output element to the XML output writer and detaches it
794     * from it's parent element.<p>
795     *
796     * @param parent the parent element
797     * @param output the output element
798     *
799     * @throws SAXException if something goes wrong processing the manifest.xml
800     */
801    protected void digestElement(Element parent, Element output) throws SAXException {
802
803        m_saxWriter.write(output);
804        parent.remove(output);
805    }
806
807    /**
808     * Exports all resources and possible sub-folders form the provided list of resources.
809     *
810     * @param parent the parent node to add the resources to
811     * @param resourcesToExport the list of resources to export
812     *
813     * @throws CmsImportExportException if something goes wrong
814     * @throws SAXException if something goes wrong processing the manifest.xml
815     * @throws IOException if not all resources could be appended to the ZIP archive
816     */
817    protected void exportAllResources(Element parent, List<String> resourcesToExport)
818    throws CmsImportExportException, IOException, SAXException {
819
820        // export all the resources
821        String resourceNodeName = getResourceNodeName();
822        m_resourceNode = parent.addElement(resourceNodeName);
823        getSaxWriter().writeOpen(m_resourceNode);
824
825        if (m_parameters.isRecursive()) {
826            String siteRoot = m_cms.getRequestContext().getSiteRoot();
827            if (siteRoot.equals("") || siteRoot.equals("/")) {
828                resourcesToExport = CmsFileUtil.removeRedundancies(resourcesToExport);
829            } else {
830                // Prevent resources in /system or other sites from being removed when '/' for current site is also selected
831                Map<String, String> rootToSitePaths = new HashMap<>();
832                for (String sitePath : resourcesToExport) {
833                    String rootPath = m_cms.addSiteRoot(sitePath);
834                    rootToSitePaths.put(rootPath, sitePath);
835                }
836                resourcesToExport = CmsFileUtil.removeRedundancies(
837                    new ArrayList<>(rootToSitePaths.keySet())).stream().map(rootToSitePaths::get).collect(
838                        Collectors.toList());
839
840            }
841        }
842
843        // distinguish folder and file names
844        List<String> folderNames = new ArrayList<String>();
845        List<String> fileNames = new ArrayList<String>();
846        Iterator<String> it = resourcesToExport.iterator();
847        while (it.hasNext()) {
848            String resource = it.next();
849            if (CmsResource.isFolder(resource)) {
850                folderNames.add(resource);
851            } else {
852                fileNames.add(resource);
853            }
854        }
855
856        m_exportedResources = new HashSet<CmsUUID>();
857
858        // export the folders
859        for (int i = 0; i < folderNames.size(); i++) {
860            String path = folderNames.get(i);
861            if (m_parameters.isRecursive()) {
862                // first add super folders to the xml-config file
863                addParentFolders(path);
864                addChildResources(path);
865            } else {
866                CmsFolder folder;
867                try {
868                    folder = getCms().readFolder(path, CmsResourceFilter.IGNORE_EXPIRATION);
869                } catch (CmsException e) {
870                    CmsMessageContainer message = Messages.get().container(
871                        Messages.ERR_IMPORTEXPORT_ERROR_ADDING_PARENT_FOLDERS_1,
872                        path);
873                    if (LOG.isDebugEnabled()) {
874                        LOG.debug(message.key(), e);
875                    }
876                    throw new CmsImportExportException(message, e);
877                }
878                CmsResourceState state = folder.getState();
879                long age = folder.getDateLastModified() < folder.getDateCreated()
880                ? folder.getDateCreated()
881                : folder.getDateLastModified();
882
883                if (getCms().getRequestContext().getCurrentProject().isOnlineProject()
884                    || (m_parameters.isIncludeUnchangedResources())
885                    || state.isNew()
886                    || state.isChanged()) {
887                    if (!state.isDeleted() && (age >= m_parameters.getContentAge())) {
888                        // check if this is a system-folder and if it should be included.
889                        String export = getCms().getSitePath(folder);
890                        if (checkExportResource(export)) {
891                            appendResourceToManifest(folder, true);
892                        }
893                    }
894                }
895            }
896        }
897        // export the files
898        addFiles(fileNames);
899
900        // write the XML
901        getSaxWriter().writeClose(m_resourceNode);
902        parent.remove(m_resourceNode);
903        m_resourceNode = null;
904    }
905
906    /**
907     * Exports one single file with all its data and content.<p>
908     *
909     * @param file the file to be exported
910     *
911     * @throws CmsImportExportException if something goes wrong
912     * @throws SAXException if something goes wrong processing the manifest.xml
913     * @throws IOException if the ZIP entry for the file could be appended to the ZIP archive
914     */
915    protected void exportFile(CmsFile file) throws CmsImportExportException, SAXException, IOException {
916
917        String source = trimResourceName(getCms().getSitePath(file));
918        I_CmsReport report = getReport();
919        m_exportCount++;
920        report.print(
921            org.opencms.report.Messages.get().container(
922                org.opencms.report.Messages.RPT_SUCCESSION_1,
923                String.valueOf(m_exportCount)),
924            I_CmsReport.FORMAT_NOTE);
925        report.print(Messages.get().container(Messages.RPT_EXPORT_0), I_CmsReport.FORMAT_NOTE);
926        report.print(
927            org.opencms.report.Messages.get().container(
928                org.opencms.report.Messages.RPT_ARGUMENT_1,
929                getCms().getSitePath(file)));
930        report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
931
932        // store content in zip-file
933        // check if the content of this resource was not already exported
934        if (!m_exportedResources.contains(file.getResourceId())) {
935            // write the file using the export writer
936            m_exportWriter.writeFile(file, source);
937            // add the resource id to the storage to mark that this resource was already exported
938            m_exportedResources.add(file.getResourceId());
939            // create the manifest-entries
940            appendResourceToManifest(file, true);
941        } else {
942            // only create the manifest-entries
943            appendResourceToManifest(file, false);
944        }
945
946        if (LOG.isInfoEnabled()) {
947            LOG.info(
948                Messages.get().getBundle().key(Messages.LOG_EXPORTING_OK_2, String.valueOf(m_exportCount), source));
949        }
950        report.println(
951            org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
952            I_CmsReport.FORMAT_OK);
953    }
954
955    /**
956     * Exports one single group with all it's data.<p>
957     *
958     * @param parent the parent node to add the groups to
959     * @param group the group to be exported
960     *
961     * @throws CmsImportExportException if something goes wrong
962     * @throws SAXException if something goes wrong processing the manifest.xml
963     */
964    protected void exportGroup(Element parent, CmsGroup group) throws CmsImportExportException, SAXException {
965
966        try {
967            String parentgroup;
968            if ((group.getParentId() == null) || group.getParentId().isNullUUID()) {
969                parentgroup = "";
970            } else {
971                parentgroup = getCms().getParent(group.getName()).getName();
972            }
973
974            Element e = parent.addElement(CmsImportVersion10.N_GROUP);
975            e.addElement(CmsImportVersion10.N_NAME).addText(group.getSimpleName());
976            e.addElement(CmsImportVersion10.N_DESCRIPTION).addCDATA(group.getDescription());
977            e.addElement(CmsImportVersion10.N_FLAGS).addText(Integer.toString(group.getFlags()));
978            e.addElement(CmsImportVersion10.N_PARENTGROUP).addText(parentgroup);
979
980            // write the XML
981            digestElement(parent, e);
982        } catch (CmsException e) {
983            CmsMessageContainer message = org.opencms.db.Messages.get().container(
984                org.opencms.db.Messages.ERR_GET_PARENT_GROUP_1,
985                group.getName());
986            if (LOG.isDebugEnabled()) {
987                LOG.debug(message.key(), e);
988            }
989
990            throw new CmsImportExportException(message, e);
991        }
992    }
993
994    /**
995     * Exports all groups of the given organizational unit.<p>
996     *
997     * @param parent the parent node to add the groups to
998     * @param orgunit the organizational unit to write the groups for
999     *
1000     * @throws CmsImportExportException if something goes wrong
1001     * @throws SAXException if something goes wrong processing the manifest.xml
1002     */
1003    protected void exportGroups(Element parent, CmsOrganizationalUnit orgunit)
1004    throws CmsImportExportException, SAXException {
1005
1006        try {
1007            I_CmsReport report = getReport();
1008            List<CmsGroup> allGroups = OpenCms.getOrgUnitManager().getGroups(getCms(), orgunit.getName(), false);
1009            for (int i = 0, l = allGroups.size(); i < l; i++) {
1010                CmsGroup group = allGroups.get(i);
1011                report.print(
1012                    org.opencms.report.Messages.get().container(
1013                        org.opencms.report.Messages.RPT_SUCCESSION_2,
1014                        String.valueOf(i + 1),
1015                        String.valueOf(l)),
1016                    I_CmsReport.FORMAT_NOTE);
1017                report.print(Messages.get().container(Messages.RPT_EXPORT_GROUP_0), I_CmsReport.FORMAT_NOTE);
1018                report.print(
1019                    org.opencms.report.Messages.get().container(
1020                        org.opencms.report.Messages.RPT_ARGUMENT_1,
1021                        group.getName()));
1022                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
1023                exportGroup(parent, group);
1024                report.println(
1025                    org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
1026                    I_CmsReport.FORMAT_OK);
1027            }
1028        } catch (CmsImportExportException e) {
1029            throw e;
1030        } catch (CmsException e) {
1031            if (LOG.isDebugEnabled()) {
1032                LOG.debug(e.getLocalizedMessage(), e);
1033            }
1034            throw new CmsImportExportException(e.getMessageContainer(), e);
1035        }
1036    }
1037
1038    /**
1039     * Exports one single organizational unit with all it's data.<p>
1040     *
1041     * @param parent the parent node to add the groups to
1042     * @param orgunit the group to be exported
1043     *
1044     * @throws SAXException if something goes wrong processing the manifest.xml
1045     * @throws CmsException if something goes wrong reading the data to export
1046     */
1047    protected void exportOrgUnit(Element parent, CmsOrganizationalUnit orgunit) throws SAXException, CmsException {
1048
1049        Element orgunitElement = parent.addElement(CmsImportVersion10.N_ORGUNIT);
1050        getSaxWriter().writeOpen(orgunitElement);
1051
1052        Element name = orgunitElement.addElement(CmsImportVersion10.N_NAME).addText(orgunit.getName());
1053        digestElement(orgunitElement, name);
1054
1055        Element description = orgunitElement.addElement(CmsImportVersion10.N_DESCRIPTION).addCDATA(
1056            orgunit.getDescription());
1057        digestElement(orgunitElement, description);
1058
1059        Element flags = orgunitElement.addElement(CmsImportVersion10.N_FLAGS).addText(
1060            Integer.toString(orgunit.getFlags()));
1061        digestElement(orgunitElement, flags);
1062
1063        Element resources = orgunitElement.addElement(CmsImportVersion10.N_RESOURCES);
1064        Iterator<CmsResource> it = OpenCms.getOrgUnitManager().getResourcesForOrganizationalUnit(
1065            getCms(),
1066            orgunit.getName()).iterator();
1067        while (it.hasNext()) {
1068            CmsResource resource = it.next();
1069            resources.addElement(CmsImportVersion10.N_RESOURCE).addText(resource.getRootPath());
1070        }
1071        digestElement(orgunitElement, resources);
1072        getReport().println(
1073            org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
1074            I_CmsReport.FORMAT_OK);
1075
1076        Element groupsElement = parent.addElement(CmsImportVersion10.N_GROUPS);
1077        getSaxWriter().writeOpen(groupsElement);
1078        exportGroups(groupsElement, orgunit);
1079        getSaxWriter().writeClose(groupsElement);
1080
1081        Element usersElement = parent.addElement(CmsImportVersion10.N_USERS);
1082        getSaxWriter().writeOpen(usersElement);
1083        exportUsers(usersElement, orgunit);
1084        getSaxWriter().writeClose(usersElement);
1085
1086        getSaxWriter().writeClose(orgunitElement);
1087    }
1088
1089    /**
1090     * Exports all organizational units with all data.<p>
1091     *
1092     * @param parent the parent node to add the organizational units to
1093     *
1094     * @throws CmsImportExportException if something goes wrong
1095     * @throws SAXException if something goes wrong processing the manifest.xml
1096     */
1097    protected void exportOrgUnits(Element parent) throws CmsImportExportException, SAXException {
1098
1099        try {
1100            Element orgunitsElement = parent.addElement(CmsImportVersion10.N_ORGUNITS);
1101            getSaxWriter().writeOpen(orgunitsElement);
1102
1103            I_CmsReport report = getReport();
1104            List<CmsOrganizationalUnit> allOUs = new ArrayList<CmsOrganizationalUnit>();
1105            allOUs.add(OpenCms.getOrgUnitManager().readOrganizationalUnit(getCms(), ""));
1106            allOUs.addAll(OpenCms.getOrgUnitManager().getOrganizationalUnits(getCms(), "", true));
1107            for (int i = 0; i < allOUs.size(); i++) {
1108                CmsOrganizationalUnit ou = allOUs.get(i);
1109                report.print(
1110                    org.opencms.report.Messages.get().container(
1111                        org.opencms.report.Messages.RPT_SUCCESSION_2,
1112                        String.valueOf(i + 1),
1113                        String.valueOf(allOUs.size())),
1114                    I_CmsReport.FORMAT_NOTE);
1115                report.print(Messages.get().container(Messages.RPT_EXPORT_ORGUNIT_0), I_CmsReport.FORMAT_NOTE);
1116                report.print(
1117                    org.opencms.report.Messages.get().container(
1118                        org.opencms.report.Messages.RPT_ARGUMENT_1,
1119                        ou.getName()));
1120                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
1121
1122                exportOrgUnit(orgunitsElement, ou);
1123            }
1124            getSaxWriter().writeClose(orgunitsElement);
1125        } catch (CmsImportExportException e) {
1126            throw e;
1127        } catch (CmsException e) {
1128            if (LOG.isDebugEnabled()) {
1129                LOG.debug(e.getLocalizedMessage(), e);
1130            }
1131            throw new CmsImportExportException(e.getMessageContainer(), e);
1132        }
1133    }
1134
1135    /**
1136     * Exports one single project with all it's data.<p>
1137     *
1138     * @param parent the parent node to add the project to
1139     * @param project the project to be exported
1140     *
1141     * @throws CmsImportExportException if something goes wrong
1142     * @throws SAXException if something goes wrong processing the manifest.xml
1143     */
1144    protected void exportProject(Element parent, CmsProject project) throws CmsImportExportException, SAXException {
1145
1146        I_CmsReport report = getReport();
1147        CmsDefaultUsers defaultUsers = OpenCms.getDefaultUsers();
1148
1149        String users;
1150        try {
1151            users = getCms().readGroup(project.getGroupId()).getName();
1152        } catch (CmsException e) {
1153            CmsMessageContainer message = org.opencms.db.Messages.get().container(
1154                org.opencms.db.Messages.ERR_READ_GROUP_FOR_ID_1,
1155                project.getGroupId());
1156            if (LOG.isDebugEnabled()) {
1157                LOG.debug(message.key(), e);
1158            }
1159
1160            users = defaultUsers.getGroupUsers();
1161            report.println(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
1162            report.print(message, I_CmsReport.FORMAT_ERROR);
1163
1164        }
1165        String managers;
1166        try {
1167            managers = getCms().readGroup(project.getManagerGroupId()).getName();
1168        } catch (CmsException e) {
1169            CmsMessageContainer message = org.opencms.db.Messages.get().container(
1170                org.opencms.db.Messages.ERR_READ_GROUP_FOR_ID_1,
1171                project.getManagerGroupId());
1172            if (LOG.isDebugEnabled()) {
1173                LOG.debug(message.key(), e);
1174            }
1175
1176            managers = defaultUsers.getGroupAdministrators();
1177            report.println(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
1178            report.print(message, I_CmsReport.FORMAT_ERROR);
1179        }
1180
1181        Element e = parent.addElement(CmsImportVersion10.N_PROJECT);
1182        e.addElement(CmsImportVersion10.N_NAME).addText(project.getSimpleName());
1183        e.addElement(CmsImportVersion10.N_DESCRIPTION).addCDATA(project.getDescription());
1184        e.addElement(CmsImportVersion10.N_USERSGROUP).addText(users);
1185        e.addElement(CmsImportVersion10.N_MANAGERSGROUP).addText(managers);
1186
1187        Element resources = e.addElement(CmsImportVersion10.N_RESOURCES);
1188        try {
1189            Iterator<String> it = getCms().readProjectResources(project).iterator();
1190            while (it.hasNext()) {
1191                String resName = it.next();
1192                resources.addElement(CmsImportVersion10.N_RESOURCE).addText(resName);
1193            }
1194        } catch (CmsException exc) {
1195            CmsMessageContainer message = org.opencms.db.Messages.get().container(
1196                org.opencms.db.Messages.ERR_READ_PROJECT_RESOURCES_2,
1197                project.getName(),
1198                project.getUuid());
1199            if (LOG.isDebugEnabled()) {
1200                LOG.debug(message.key(), exc);
1201            }
1202
1203            throw new CmsImportExportException(message, exc);
1204        }
1205        // write the XML
1206        digestElement(parent, e);
1207    }
1208
1209    /**
1210     * Exports all projects with all data.<p>
1211     *
1212     * @param parent the parent node to add the projects to
1213     *
1214     * @throws CmsImportExportException if something goes wrong
1215     * @throws SAXException if something goes wrong processing the manifest.xml
1216     */
1217    protected void exportProjects(Element parent) throws CmsImportExportException, SAXException {
1218
1219        try {
1220            I_CmsReport report = getReport();
1221            List<CmsProject> allProjects = OpenCms.getOrgUnitManager().getAllManageableProjects(getCms(), "", true);
1222            for (int i = 0; i < allProjects.size(); i++) {
1223                CmsProject project = allProjects.get(i);
1224                report.print(
1225                    org.opencms.report.Messages.get().container(
1226                        org.opencms.report.Messages.RPT_SUCCESSION_2,
1227                        String.valueOf(i + 1),
1228                        String.valueOf(allProjects.size())),
1229                    I_CmsReport.FORMAT_NOTE);
1230                report.print(Messages.get().container(Messages.RPT_EXPORT_PROJECT_0), I_CmsReport.FORMAT_NOTE);
1231                report.print(
1232                    org.opencms.report.Messages.get().container(
1233                        org.opencms.report.Messages.RPT_ARGUMENT_1,
1234                        project.getName()));
1235
1236                exportProject(parent, project);
1237
1238                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
1239                report.println(
1240                    org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
1241                    I_CmsReport.FORMAT_OK);
1242            }
1243        } catch (CmsImportExportException e) {
1244            throw e;
1245        } catch (CmsException e) {
1246            if (LOG.isDebugEnabled()) {
1247                LOG.debug(e.getLocalizedMessage(), e);
1248            }
1249            throw new CmsImportExportException(e.getMessageContainer(), e);
1250        }
1251    }
1252
1253    /**
1254     * Exports one single user with all its data.<p>
1255     *
1256     * @param parent the parent node to add the users to
1257     * @param user the user to be exported
1258     *
1259     * @throws CmsImportExportException if something goes wrong
1260     * @throws SAXException if something goes wrong processing the manifest.xml
1261     */
1262    protected void exportUser(Element parent, CmsUser user) throws CmsImportExportException, SAXException {
1263
1264        try {
1265            // add user node to the manifest.xml
1266            Element e = parent.addElement(CmsImportVersion10.N_USER);
1267            e.addElement(CmsImportVersion10.N_NAME).addText(user.getSimpleName());
1268            // encode the password, using a base 64 decoder
1269            String passwd = new String(Base64.encodeBase64(user.getPassword().getBytes()));
1270            e.addElement(CmsImportVersion10.N_PASSWORD).addCDATA(passwd);
1271            e.addElement(CmsImportVersion10.N_FIRSTNAME).addText(user.getFirstname());
1272            e.addElement(CmsImportVersion10.N_LASTNAME).addText(user.getLastname());
1273            e.addElement(CmsImportVersion10.N_EMAIL).addText(user.getEmail());
1274            e.addElement(CmsImportVersion10.N_FLAGS).addText(Integer.toString(user.getFlags()));
1275            e.addElement(CmsImportVersion10.N_DATECREATED).addText(Long.toString(user.getDateCreated()));
1276
1277            Element userInfoNode = e.addElement(CmsImportVersion10.N_USERINFO);
1278            List<String> keys = new ArrayList<String>(user.getAdditionalInfo().keySet());
1279            Collections.sort(keys);
1280            Iterator<String> itInfoKeys = keys.iterator();
1281            while (itInfoKeys.hasNext()) {
1282                String key = itInfoKeys.next();
1283                if (key == null) {
1284                    continue;
1285                }
1286                Object value = user.getAdditionalInfo(key);
1287                if (value == null) {
1288                    continue;
1289                }
1290                Element entryNode = userInfoNode.addElement(CmsImportVersion10.N_USERINFO_ENTRY);
1291                entryNode.addAttribute(CmsImportVersion10.A_NAME, key);
1292                entryNode.addAttribute(CmsImportVersion10.A_TYPE, value.getClass().getName());
1293                try {
1294                    // serialize the user info and write it into a file
1295                    entryNode.addCDATA(CmsDataTypeUtil.dataExport(value));
1296                } catch (IOException ioe) {
1297                    getReport().println(ioe);
1298                    if (LOG.isErrorEnabled()) {
1299                        LOG.error(
1300                            Messages.get().getBundle().key(
1301                                Messages.ERR_IMPORTEXPORT_ERROR_EXPORTING_USER_1,
1302                                user.getName()),
1303                            ioe);
1304                    }
1305                }
1306            }
1307
1308            // append node for roles of user
1309            Element userRoles = e.addElement(CmsImportVersion10.N_USERROLES);
1310            List<CmsRole> roles = OpenCms.getRoleManager().getRolesOfUser(
1311                getCms(),
1312                user.getName(),
1313                "",
1314                true,
1315                true,
1316                true);
1317            for (int i = 0; i < roles.size(); i++) {
1318                String roleName = roles.get(i).getFqn();
1319                userRoles.addElement(CmsImportVersion10.N_USERROLE).addText(roleName);
1320            }
1321            // append the node for groups of user
1322            Element userGroups = e.addElement(CmsImportVersion10.N_USERGROUPS);
1323            List<CmsGroup> groups = getCms().getGroupsOfUser(user.getName(), true, true);
1324            for (int i = 0; i < groups.size(); i++) {
1325                String groupName = groups.get(i).getName();
1326                userGroups.addElement(CmsImportVersion10.N_USERGROUP).addText(groupName);
1327            }
1328            // write the XML
1329            digestElement(parent, e);
1330        } catch (CmsException e) {
1331            if (LOG.isDebugEnabled()) {
1332                LOG.debug(e.getLocalizedMessage(), e);
1333            }
1334            throw new CmsImportExportException(e.getMessageContainer(), e);
1335        }
1336    }
1337
1338    /**
1339     * Exports all users of the given organizational unit.<p>
1340     *
1341     * @param parent the parent node to add the users to
1342     * @param orgunit the organizational unit to write the groups for
1343     *
1344     * @throws CmsImportExportException if something goes wrong
1345     * @throws SAXException if something goes wrong processing the manifest.xml
1346     */
1347    protected void exportUsers(Element parent, CmsOrganizationalUnit orgunit)
1348    throws CmsImportExportException, SAXException {
1349
1350        try {
1351            I_CmsReport report = getReport();
1352            List<CmsUser> allUsers = OpenCms.getOrgUnitManager().getUsers(getCms(), orgunit.getName(), false);
1353            for (int i = 0, l = allUsers.size(); i < l; i++) {
1354                CmsUser user = allUsers.get(i);
1355                report.print(
1356                    org.opencms.report.Messages.get().container(
1357                        org.opencms.report.Messages.RPT_SUCCESSION_2,
1358                        String.valueOf(i + 1),
1359                        String.valueOf(l)),
1360                    I_CmsReport.FORMAT_NOTE);
1361                report.print(Messages.get().container(Messages.RPT_EXPORT_USER_0), I_CmsReport.FORMAT_NOTE);
1362                report.print(
1363                    org.opencms.report.Messages.get().container(
1364                        org.opencms.report.Messages.RPT_ARGUMENT_1,
1365                        user.getName()));
1366                report.print(org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_DOTS_0));
1367                exportUser(parent, user);
1368                report.println(
1369                    org.opencms.report.Messages.get().container(org.opencms.report.Messages.RPT_OK_0),
1370                    I_CmsReport.FORMAT_OK);
1371            }
1372        } catch (CmsImportExportException e) {
1373            throw e;
1374        } catch (CmsException e) {
1375            if (LOG.isDebugEnabled()) {
1376                LOG.debug(e.getLocalizedMessage(), e);
1377            }
1378            throw new CmsImportExportException(e.getMessageContainer(), e);
1379        }
1380    }
1381
1382    /**
1383     * Check, if the resource should be exported with minimal meta-data.
1384     * This holds for resources that are not part of the export, but must be
1385     * exported as super-folders.
1386     *
1387     * @param path export-site relative path of the resource to check.
1388     *
1389     * @return flag, indicating if the resource should be exported with minimal meta data.
1390     */
1391    protected boolean exportWithMinimalMetaData(String path) {
1392
1393        String checkPath = path.startsWith("/") ? path + "/" : "/" + path + "/";
1394        for (String p : m_parameters.getResourcesToExportWithMetaData()) {
1395            if (checkPath.startsWith(p)) {
1396                return false;
1397            }
1398        }
1399        return true;
1400    }
1401
1402    /**
1403     * Returns the OpenCms context object this export was initialized with.<p>
1404     *
1405     * @return the OpenCms context object this export was initialized with
1406     */
1407    protected CmsObject getCms() {
1408
1409        return m_cms;
1410    }
1411
1412    /**
1413     * Returns the name of the export file.<p>
1414     *
1415     * @return the name of the export file
1416     */
1417    protected String getExportFileName() {
1418
1419        return m_parameters.getPath();
1420    }
1421
1422    /**
1423     * Returns the name of the main export node.<p>
1424     *
1425     * @return the name of the main export node
1426     */
1427    protected String getExportNodeName() {
1428
1429        return CmsImportExportManager.N_EXPORT;
1430    }
1431
1432    /**
1433     * Returns the report to write progress messages to.<p>
1434     *
1435     * @return the report to write progress messages to
1436     */
1437    protected I_CmsReport getReport() {
1438
1439        return m_report;
1440    }
1441
1442    /**
1443     * Returns the name for the main resource node.<p>
1444     *
1445     * @return the name for the main resource node
1446     */
1447    protected String getResourceNodeName() {
1448
1449        return "files";
1450    }
1451
1452    /**
1453     * Returns the SAX based xml writer to write the XML output to.<p>
1454     *
1455     * @return the SAX based xml writer to write the XML output to
1456     */
1457    protected SAXWriter getSaxWriter() {
1458
1459        return m_saxWriter;
1460    }
1461
1462    /**
1463     * Checks if a property should be written to the export or not.<p>
1464     *
1465     * @param property the property to check
1466     *
1467     * @return if true, the property is to be ignored, otherwise it should be exported
1468     */
1469    protected boolean isIgnoredProperty(CmsProperty property) {
1470
1471        if (property == null) {
1472            return true;
1473        }
1474        if (property.getName().equals(CmsPropertyDefinition.PROPERTY_EXPORT_TYPE)) {
1475            return true;
1476        }
1477        return false;
1478    }
1479
1480    /**
1481     * Checks if a resource is belongs to the correct project for exporting.<p>
1482     *
1483     * @param res the resource to check
1484     *
1485     * @return <code>true</code>, if the resource can be exported, false otherwise
1486     */
1487    protected boolean isInExportableProject(CmsResource res) {
1488
1489        boolean retValue = true;
1490        // the "only modified in current project flag" is checked
1491        if (m_parameters.isInProject()) {
1492            // resource state is new or changed
1493            if ((res.getState() == CmsResource.STATE_CHANGED) || (res.getState() == CmsResource.STATE_NEW)) {
1494                // the resource belongs not to the current project, so it must not be exported
1495                if (!res.getProjectLastModified().equals(getCms().getRequestContext().getCurrentProject().getUuid())) {
1496                    retValue = false;
1497                }
1498            } else {
1499                // state is unchanged, so do not export it
1500                retValue = false;
1501            }
1502        }
1503        return retValue;
1504    }
1505
1506    /**
1507     * Opens the export ZIP file and initializes the internal XML document for the manifest.<p>
1508     * @param exportMode the export mode to use.
1509     *
1510     * @return the node in the XML document where all files are appended to
1511     *
1512     * @throws SAXException if something goes wrong processing the manifest.xml
1513     * @throws IOException if something goes wrong while closing the export file
1514     */
1515    protected Element openExportFile(ExportMode exportMode) throws IOException, SAXException {
1516
1517        // create the export writer
1518        m_exportWriter = new CmsExportHelper(
1519            getExportFileName(),
1520            m_parameters.isExportAsFiles(),
1521            m_parameters.isXmlValidation());
1522        // initialize the dom4j writer object as member variable
1523        setSaxWriter(m_exportWriter.getSaxWriter());
1524
1525        // the node in the XML document where the file entries are appended to
1526        String exportNodeName = getExportNodeName();
1527        // the XML document to write the XMl to
1528        Document doc = DocumentHelper.createDocument();
1529        // add main export node to XML document
1530        Element exportNode = doc.addElement(exportNodeName);
1531        getSaxWriter().writeOpen(exportNode);
1532
1533        // add the info element. it contains all infos for this export
1534        Element info = exportNode.addElement(CmsImportExportManager.N_INFO);
1535        if (!exportMode.equals(ExportMode.REDUCED)) {
1536            info.addElement(CmsImportExportManager.N_CREATOR).addText(
1537                getCms().getRequestContext().getCurrentUser().getName());
1538            info.addElement(CmsImportExportManager.N_OC_VERSION).addText(OpenCms.getSystemInfo().getVersionNumber());
1539            info.addElement(CmsImportExportManager.N_DATE).addText(
1540                CmsDateUtil.getHeaderDate(System.currentTimeMillis()));
1541        }
1542        info.addElement(CmsImportExportManager.N_INFO_PROJECT).addText(
1543            getCms().getRequestContext().getCurrentProject().getName());
1544        info.addElement(CmsImportExportManager.N_VERSION).addText(CmsImportExportManager.EXPORT_VERSION);
1545
1546        // write the XML
1547        digestElement(exportNode, info);
1548
1549        return exportNode;
1550    }
1551
1552    /**
1553     * Sets the SAX based XML writer to write the XML output to.<p>
1554     *
1555     * @param saxWriter the SAX based XML writer to write the XML output to
1556     */
1557    protected void setSaxWriter(SAXWriter saxWriter) {
1558
1559        m_saxWriter = saxWriter;
1560    }
1561
1562    /**
1563     * Cuts leading and trailing '/' from the given resource name.<p>
1564     *
1565     * @param resourceName the absolute path of a resource
1566     *
1567     * @return the trimmed resource name
1568     */
1569    protected String trimResourceName(String resourceName) {
1570
1571        if (resourceName.startsWith("/")) {
1572            resourceName = resourceName.substring(1);
1573        }
1574        if (resourceName.endsWith("/")) {
1575            resourceName = resourceName.substring(0, resourceName.length() - 1);
1576        }
1577        return resourceName;
1578    }
1579
1580    /** Returns the manifest entry for the <code>&lt;datelastmodified&gt;</code> node of the resource.
1581     * Depending on the export.timestamp property, the time stamp from the VFS (default) or
1582     * special macros are used.
1583     *
1584     * @param resource the resource for which the manifest entry is generated
1585     * @return the time stamp or macro to write as value for <code>&lt;datelastmodified&gt;</code>
1586     */
1587    private String getDateLastModifiedForExport(final CmsResource resource) {
1588
1589        TimestampMode timeMode = TimestampMode.VFSTIME;
1590        String typeName = OpenCms.getResourceManager().getResourceType(resource).getTypeName();
1591        TimestampMode defaultModeForResourceType = OpenCms.getImportExportManager().getDefaultTimestampMode(typeName);
1592        if (null == defaultModeForResourceType) {
1593            try {
1594                CmsProperty exporttimeProp = m_cms.readPropertyObject(
1595                    resource,
1596                    CmsImportExportManager.PROP_EXPORT_TIMESTAMP,
1597                    true);
1598                if (TimestampMode.FILETIME.equals(TimestampMode.getEnum(exporttimeProp.getValue()))) {
1599                    timeMode = TimestampMode.FILETIME;
1600                } else if (TimestampMode.IMPORTTIME.equals(TimestampMode.getEnum(exporttimeProp.getValue()))) {
1601                    timeMode = TimestampMode.IMPORTTIME;
1602                }
1603            } catch (@SuppressWarnings("unused") CmsException e) {
1604                // Do nothing, use default mode
1605            }
1606        } else {
1607            timeMode = defaultModeForResourceType;
1608        }
1609        switch (timeMode) {
1610            case FILETIME:
1611                return CmsMacroResolver.formatMacro(CmsImportExportManager.TimestampMode.FILETIME.toString());
1612            case IMPORTTIME:
1613                return CmsMacroResolver.formatMacro(CmsImportExportManager.TimestampMode.IMPORTTIME.toString());
1614            case VFSTIME:
1615            default:
1616                return CmsDateUtil.getHeaderDate(resource.getDateLastModified());
1617        }
1618
1619    }
1620}