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