001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software GmbH & Co. KG, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.db;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsProject;
032import org.opencms.file.CmsResource;
033import org.opencms.file.CmsResourceFilter;
034import org.opencms.file.CmsVfsResourceNotFoundException;
035import org.opencms.file.I_CmsResource;
036import org.opencms.main.CmsException;
037import org.opencms.main.CmsIllegalArgumentException;
038import org.opencms.main.CmsLog;
039import org.opencms.main.OpenCms;
040import org.opencms.util.CmsFileUtil;
041import org.opencms.util.CmsUUID;
042
043import java.io.Externalizable;
044import java.io.IOException;
045import java.io.ObjectInput;
046import java.io.ObjectOutput;
047import java.util.ArrayList;
048import java.util.Collection;
049import java.util.Collections;
050import java.util.HashMap;
051import java.util.HashSet;
052import java.util.Iterator;
053import java.util.List;
054import java.util.Map;
055import java.util.Set;
056
057import org.apache.commons.logging.Log;
058
059/**
060 * A container for all new/changed/deteled Cms resources that are published together.<p>
061 *
062 * Only classes inside the org.opencms.db package can add or remove elements to or from this list.
063 * This allows the OpenCms API to pass the list around between classes, but with restricted access to
064 * create this list.<p>
065 *
066 * To create a publish list, one of the public constructors must be used in order to set the basic operation mode
067 * (project publish or direct publish).
068 * After this, use <code>{@link org.opencms.db.CmsDriverManager#fillPublishList(CmsDbContext, CmsPublishList)}</code>
069 * to fill the actual values of the publish list.<p>
070 *
071 * @since 6.0.0
072 *
073 * @see org.opencms.db.CmsDriverManager#fillPublishList(CmsDbContext, CmsPublishList)
074 */
075public class CmsPublishList implements Externalizable {
076
077    /** The log object for this class. */
078    private static final Log LOG = CmsLog.getLog(CmsPublishList.class);
079
080    /** Indicates a non existent object in the serialized data. */
081    private static final int NIL = -1;
082
083    /** Serial version UID required for safe serialization. */
084    private static final long serialVersionUID = -2578909250462750927L;
085
086    /** Length of a serialized uuid. */
087    private static final int UUID_LENGTH = CmsUUID.getNullUUID().toByteArray().length;
088
089    /** The list of deleted folder resources to be published.<p> */
090    private List<CmsResource> m_deletedFolderList;
091
092    /** The list of deleted folder UUIDs to be published for later retrieval.<p> */
093    private List<CmsUUID> m_deletedFolderUUIDs;
094
095    /** The list of direct publish resources. */
096    private List<CmsResource> m_directPublishResources;
097
098    /** The list of direct publish resource UUIDs to be published for later retrieval.<p> */
099    private List<CmsUUID> m_directPublishResourceUUIDs;
100
101    /** The list of new/changed/deleted file resources to be published.<p> */
102    private List<CmsResource> m_fileList;
103
104    /** The list of new/changed/deleted file resource UUIDs to be published for later retrieval.<p> */
105    private List<CmsUUID> m_fileUUIDs;
106
107    /** The list of new/changed folder resources to be published.<p> */
108    private List<CmsResource> m_folderList;
109
110    /** The list of new/changed folder resource UUIDs to be published for later retrieval.<p> */
111    private List<CmsUUID> m_folderUUIDs;
112
113    /** Indicates whether this is a user publish list. */
114    private boolean m_isUserPublishList;
115
116    /** Flag to indicate if the list needs to be revived. */
117    private boolean m_needsRevive;
118
119    /** The id of the project that is to be published. */
120    private CmsUUID m_projectId;
121
122    /** The publish history ID.<p> */
123    private CmsUUID m_publishHistoryId;
124
125    /** Indicates if siblings of the resources in the list should also be published. */
126    private boolean m_publishSiblings;
127
128    /** Indicates if sub-resources in folders should be published (for direct publish only). */
129    private boolean m_publishSubResources;
130
131    /**
132     * Empty constructor.<p>
133     */
134    public CmsPublishList() {
135
136        // noop
137    }
138
139    /**
140     * Constructs a publish list for a list of direct publish resources.<p>
141     *
142     * @param all no redundant resource are filtered out
143     * @param directPublishResources a list of <code>{@link CmsResource}</code> instances to be published directly
144     * @param directPublishSiblings indicates if all siblings of the selected resources should be published
145     */
146    public CmsPublishList(boolean all, List<CmsResource> directPublishResources, boolean directPublishSiblings) {
147
148        this(null, directPublishResources, directPublishSiblings, false, true);
149    }
150
151    /**
152     * Constructs a publish list for a given project.<p>
153     *
154     * @param project the project to publish, this should always be the id of the current project
155     */
156    public CmsPublishList(CmsProject project) {
157
158        this(project, null, false, true, false);
159    }
160
161    /**
162     * Constructs a publish list for a single direct publish resource.<p>
163     *
164     * @param directPublishResource a VFS resource to be published directly
165     * @param publishSiblings indicates if all siblings of the selected resources should be published
166     */
167    public CmsPublishList(CmsResource directPublishResource, boolean publishSiblings) {
168
169        this(null, Collections.singletonList(directPublishResource), publishSiblings, true, false);
170    }
171
172    /**
173     * Constructs a publish list for a list of direct publish resources.<p>
174     *
175     * @param directPublishResources a list of <code>{@link CmsResource}</code> instances to be published directly
176     * @param publishSiblings indicates if all siblings of the selected resources should be published
177     */
178    public CmsPublishList(List<CmsResource> directPublishResources, boolean publishSiblings) {
179
180        this(null, directPublishResources, publishSiblings, true, false);
181    }
182
183    /**
184     * Constructs a publish list for a list of direct publish resources.<p>
185     *
186     * @param directPublishResources a list of <code>{@link CmsResource}</code> instances to be published directly
187     * @param publishSiblings indicates if all siblings of the selected resources should be published
188     * @param publishSubResources indicates if sub-resources in folders should be published (for direct publish only)
189     */
190    public CmsPublishList(
191        List<CmsResource> directPublishResources,
192        boolean publishSiblings,
193        boolean publishSubResources) {
194
195        this(null, directPublishResources, publishSiblings, publishSubResources, false);
196    }
197
198    /**
199     * Internal constructor for a publish list.<p>
200     *
201     * @param project the project to publish
202     * @param directPublishResources the list of direct publish resources
203     * @param publishSiblings indicates if all siblings of the selected resources should be published
204     * @param publishSubResources indicates if sub-resources in folders should be published (for direct publish only)
205     * @param all if <code>true</code> the publish list will not be filtered for redundant resources
206     */
207    private CmsPublishList(
208        CmsProject project,
209        List<CmsResource> directPublishResources,
210        boolean publishSiblings,
211        boolean publishSubResources,
212        boolean all) {
213
214        m_fileList = new ArrayList<CmsResource>();
215        m_folderList = new ArrayList<CmsResource>();
216        m_deletedFolderList = new ArrayList<CmsResource>();
217        m_publishHistoryId = new CmsUUID();
218        m_publishSiblings = publishSiblings;
219        m_publishSubResources = publishSubResources;
220        m_projectId = (project != null) ? project.getUuid() : null;
221        if (directPublishResources != null) {
222            if (!all) {
223                // reduce list of folders to minimum
224                m_directPublishResources = Collections.unmodifiableList(
225                    CmsFileUtil.removeRedundantResources(directPublishResources));
226            } else {
227                m_directPublishResources = Collections.unmodifiableList(directPublishResources);
228            }
229        }
230    }
231
232    /**
233     * Returns a list of all resources in the publish list,
234     * including folders and files.<p>
235     *
236     * @return a list of {@link CmsResource} objects
237     */
238    public List<CmsResource> getAllResources() {
239
240        List<CmsResource> all = new ArrayList<CmsResource>();
241        all.addAll(m_folderList);
242        all.addAll(m_fileList);
243        all.addAll(m_deletedFolderList);
244
245        Collections.sort(all, I_CmsResource.COMPARE_ROOT_PATH);
246        return Collections.unmodifiableList(all);
247    }
248
249    /**
250     * Returns a list of folder resources with the deleted state.<p>
251     *
252     * @return a list of folder resources with the deleted state
253     */
254    public List<CmsResource> getDeletedFolderList() {
255
256        if (m_needsRevive) {
257            return null;
258        } else {
259            return m_deletedFolderList;
260        }
261    }
262
263    /**
264     * Returns the list of resources that should be published for a "direct" publish operation.<p>
265     *
266     * Will return <code>null</code> if this publish list was not initialized for a "direct publish" but
267     * for a project publish.<p>
268     *
269     * @return the list of resources that should be published for a "direct" publish operation, or <code>null</code>
270     */
271    public List<CmsResource> getDirectPublishResources() {
272
273        if (m_needsRevive) {
274            return null;
275        } else {
276            return m_directPublishResources;
277        }
278    }
279
280    /**
281     * Returns an unmodifiable list of the files in this publish list.<p>
282     *
283     * @return the list with the files in this publish list
284     */
285    public List<CmsResource> getFileList() {
286
287        if (m_needsRevive) {
288            return null;
289        } else {
290            return Collections.unmodifiableList(m_fileList);
291        }
292    }
293
294    /**
295     * Returns an unmodifiable list of the new/changed folders in this publish list.<p>
296     *
297     * @return the list with the new/changed folders in this publish list
298     */
299    public List<CmsResource> getFolderList() {
300
301        if (m_needsRevive) {
302            return null;
303        } else {
304            return Collections.unmodifiableList(m_folderList);
305        }
306    }
307
308    /**
309     * Returns the id of the project that should be published, or <code>-1</code> if this publish list
310     * is initialized for a "direct publish" operation.<p>
311     *
312     * @return the id of the project that should be published, or <code>-1</code>
313     */
314    public CmsUUID getProjectId() {
315
316        return m_projectId;
317    }
318
319    /**
320     * Returns the publish history Id for this publish list.<p>
321     *
322     * @return the publish history Id
323     */
324    public CmsUUID getPublishHistoryId() {
325
326        return m_publishHistoryId;
327    }
328
329    /**
330     * Gets the list of moved folders which are not subfolders of other moved folders in the publish list.<p>
331     * @param cms the current cms context
332     * @return the moved folders which are not subfolders of other moved folders in the publish list
333     * @throws CmsException if something goes wrong
334     */
335    public List<CmsResource> getTopMovedFolders(CmsObject cms) throws CmsException {
336
337        List<CmsResource> movedFolders = getMovedFolders(cms);
338        List<CmsResource> result = getTopFolders(movedFolders);
339        return result;
340    }
341
342    /**
343     * Checks if this is a publish list is used for a "direct publish" operation.<p>
344     *
345     * @return true if this is a publish list is used for a "direct publish" operation
346     */
347    public boolean isDirectPublish() {
348
349        return (m_projectId == null);
350    }
351
352    /**
353     * Returns <code>true</code> if all siblings of the project resources are to be published.<p>
354     *
355     * @return <code>true</code> if all siblings of the project resources are to be publisheds
356     */
357    public boolean isPublishSiblings() {
358
359        return m_publishSiblings;
360    }
361
362    /**
363     * Returns <code>true</code> if sub-resources in folders should be published (for direct publish only).<p>
364     *
365     * @return <code>true</code> if sub-resources in folders should be published (for direct publish only)
366     */
367    public boolean isPublishSubResources() {
368
369        return m_publishSubResources;
370    }
371
372    /**
373     * Returns true if this is a user publish list.<p>
374     *
375     * @return true if this is a user publish list
376     */
377    public boolean isUserPublishList() {
378
379        return m_isUserPublishList;
380    }
381
382    /**
383     * @see java.io.Externalizable#readExternal(java.io.ObjectInput)
384     */
385    public void readExternal(ObjectInput in) throws IOException {
386
387        // read the history id
388        m_publishHistoryId = internalReadUUID(in);
389        // read the project id
390        m_projectId = internalReadUUID(in);
391        if (m_projectId.isNullUUID()) {
392            m_projectId = null;
393        }
394        // read the flags
395        m_publishSiblings = (in.readInt() != 0);
396        m_publishSubResources = (in.readInt() != 0);
397        // read the list of direct published resources
398        m_directPublishResourceUUIDs = internalReadUUIDList(in);
399        // read the list of published files
400        m_fileUUIDs = internalReadUUIDList(in);
401        // read the list of published folders
402        m_folderUUIDs = internalReadUUIDList(in);
403        // read the list of deleted folders
404        m_deletedFolderUUIDs = internalReadUUIDList(in);
405        // set revive flag to indicate that resource lists must be revived
406        m_needsRevive = true;
407    }
408
409    /**
410     * Revives the publish list by populating the internal resource lists with <code>{@link CmsResource}</code> instances.<p>
411     *
412     * @param cms a cms object used to read the resource instances
413     */
414    public void revive(CmsObject cms) {
415
416        if (m_needsRevive) {
417            if (m_directPublishResourceUUIDs != null) {
418                m_directPublishResources = internalReadResourceList(cms, m_directPublishResourceUUIDs);
419            }
420            if (m_fileUUIDs != null) {
421                m_fileList = internalReadResourceList(cms, m_fileUUIDs);
422            }
423            if (m_folderUUIDs != null) {
424                m_folderList = internalReadResourceList(cms, m_folderUUIDs);
425            }
426            if (m_deletedFolderUUIDs != null) {
427                m_deletedFolderList = internalReadResourceList(cms, m_deletedFolderUUIDs);
428            }
429            m_needsRevive = false;
430        }
431    }
432
433    /**
434     * Sets the 'user publish list' flag on this publish list.<p>
435     *
436     * @param isUserPublishList if true, the list is marked as a user publish list
437     */
438    public void setUserPublishList(boolean isUserPublishList) {
439
440        m_isUserPublishList = isUserPublishList;
441    }
442
443    /**
444     * Returns the number of all resources to be published.<p>
445     *
446     * @return the number of all resources to be published
447     */
448    public int size() {
449
450        if (m_needsRevive) {
451            return 0;
452        } else {
453            return m_folderList.size() + m_fileList.size() + m_deletedFolderList.size();
454        }
455    }
456
457    /**
458     * @see java.lang.Object#toString()
459     */
460    @Override
461    public String toString() {
462
463        StringBuffer result = new StringBuffer();
464        result.append("\n[\n");
465        if (isDirectPublish()) {
466            result.append("direct publish of resources: ").append(m_directPublishResources.toString()).append("\n");
467        } else {
468            result.append("publish of project: ").append(m_projectId).append("\n");
469        }
470        result.append("publish history ID: ").append(m_publishHistoryId.toString()).append("\n");
471        result.append("resources: ").append(m_fileList.toString()).append("\n");
472        result.append("folders: ").append(m_folderList.toString()).append("\n");
473        result.append("deletedFolders: ").append(m_deletedFolderList.toString()).append("\n");
474        result.append("]\n");
475        return result.toString();
476    }
477
478    /**
479     * @see java.io.Externalizable#writeExternal(java.io.ObjectOutput)
480     */
481    public void writeExternal(ObjectOutput out) throws IOException {
482
483        // write the history id
484        out.write(m_publishHistoryId.toByteArray());
485        // write the project id
486        out.write((m_projectId != null) ? m_projectId.toByteArray() : CmsUUID.getNullUUID().toByteArray());
487        // write the flags
488        out.writeInt((m_publishSiblings) ? 1 : 0);
489        out.writeInt((m_publishSubResources) ? 1 : 0);
490        // write the list of direct publish resources by writing the uuid of each resource
491        if (m_directPublishResources != null) {
492            out.writeInt(m_directPublishResources.size());
493            for (Iterator<CmsResource> i = m_directPublishResources.iterator(); i.hasNext();) {
494                out.write((i.next()).getStructureId().toByteArray());
495            }
496        } else {
497            out.writeInt(NIL);
498        }
499        // write the list of published files by writing the uuid of each resource
500        if (m_fileList != null) {
501            out.writeInt(m_fileList.size());
502            for (Iterator<CmsResource> i = m_fileList.iterator(); i.hasNext();) {
503                out.write((i.next()).getStructureId().toByteArray());
504            }
505        } else {
506            out.writeInt(NIL);
507        }
508        // write the list of published folders by writing the uuid of each resource
509        if (m_folderList != null) {
510            out.writeInt(m_folderList.size());
511            for (Iterator<CmsResource> i = m_folderList.iterator(); i.hasNext();) {
512                out.write((i.next()).getStructureId().toByteArray());
513            }
514        } else {
515            out.writeInt(NIL);
516        }
517        // write the list of deleted folders by writing the uuid of each resource
518        if (m_deletedFolderList != null) {
519            out.writeInt(m_deletedFolderList.size());
520            for (Iterator<CmsResource> i = m_deletedFolderList.iterator(); i.hasNext();) {
521                out.write((i.next()).getStructureId().toByteArray());
522            }
523        } else {
524            out.writeInt(NIL);
525        }
526    }
527
528    /**
529     * Adds a new/changed Cms folder resource to the publish list.<p>
530     *
531     * @param resource a new/changed Cms folder resource
532     * @param check if set an exception is thrown if the specified resource is unchanged,
533     *              if not set the resource is ignored
534     *
535     * @throws IllegalArgumentException if the specified resource is unchanged
536     */
537    protected void add(CmsResource resource, boolean check) throws IllegalArgumentException {
538
539        if (check) {
540            // it is essential that this method is only visible within the db package!
541            if (resource.getState().isUnchanged()) {
542                throw new CmsIllegalArgumentException(
543                    Messages.get().container(Messages.ERR_PUBLISH_UNCHANGED_RESOURCE_1, resource.getRootPath()));
544            }
545        }
546        if (resource.isFolder()) {
547            if (resource.getState().isDeleted()) {
548                if (!m_deletedFolderList.contains(resource)) {
549                    // only add files not already contained in the list
550                    m_deletedFolderList.add(resource);
551                }
552            } else {
553                if (!m_folderList.contains(resource)) {
554                    // only add files not already contained in the list
555                    m_folderList.add(resource);
556                }
557            }
558        } else {
559            if (!m_fileList.contains(resource)) {
560                // only add files not already contained in the list
561                // this is required to make sure no siblings are duplicated
562                m_fileList.add(resource);
563            }
564        }
565    }
566
567    /**
568     * Appends all the given resources to this publish list.<p>
569     *
570     * @param resources resources to be added to this publish list
571     * @param check if set an exception is thrown if the a resource is unchanged,
572     *              if not set the resource is ignored
573     *
574     * @throws IllegalArgumentException if one of the resources is unchanged
575     */
576    protected void addAll(Collection<CmsResource> resources, boolean check) throws IllegalArgumentException {
577
578        // it is essential that this method is only visible within the db package!
579        Iterator<CmsResource> i = resources.iterator();
580        while (i.hasNext()) {
581            add(i.next(), check);
582        }
583    }
584
585    /**
586     * Checks whether the publish list contains all sub-resources of a list of folders.<p>
587     *
588     * @param cms the current CMS context
589     * @param folders the folders which should be checked
590     * @return a folder from the list if one of its sub-resources is not contained in the publish list, otherwise null
591     *
592     * @throws CmsException if something goes wrong
593     */
594    protected CmsResource checkContainsSubResources(CmsObject cms, List<CmsResource> folders) throws CmsException {
595
596        for (CmsResource folder : folders) {
597
598            if (!containsSubResources(cms, folder)) {
599                return folder;
600            }
601        }
602        return null;
603    }
604
605    /**
606     * Checks if the publish list contains a resource.<p>
607     *
608     * @param res the resource
609     * @return true if the publish list contains a resource
610     */
611    protected boolean containsResource(CmsResource res) {
612
613        return m_deletedFolderList.contains(res) || m_folderList.contains(res) || m_fileList.contains(res);
614    }
615
616    /**
617     * Checks if the publish list contains all sub-resources of a given folder.<p>
618     *
619     * @param cms the current CMS context
620     * @param folder the folder for which the check should be performed
621     * @return true if the publish list contains all sub-resources of a given folder
622     * @throws CmsException if something goes wrong
623     */
624    protected boolean containsSubResources(CmsObject cms, CmsResource folder) throws CmsException {
625
626        String folderPath = cms.getSitePath(folder);
627        List<CmsResource> subResources = cms.readResources(folderPath, CmsResourceFilter.ALL, true);
628        for (CmsResource resource : subResources) {
629            if (!containsResource(resource)) {
630                return false;
631            }
632        }
633        return true;
634    }
635
636    /**
637     * Gets the sub-resources of a list of folders which are missing from the publish list.<p>
638     *
639     * @param cms the current CMS context
640     * @param folders the folders which should be checked
641     * @return a list of missing sub resources
642     *
643     * @throws CmsException if something goes wrong
644     */
645    protected List<CmsResource> getMissingSubResources(CmsObject cms, List<CmsResource> folders) throws CmsException {
646
647        List<CmsResource> result = new ArrayList<CmsResource>();
648        CmsObject rootCms = OpenCms.initCmsObject(cms);
649        rootCms.getRequestContext().setSiteRoot("");
650        for (CmsResource folder : folders) {
651            List<CmsResource> subResources = rootCms.readResources(folder.getRootPath(), CmsResourceFilter.ALL, true);
652            for (CmsResource resource : subResources) {
653                if (!containsResource(resource) && !resource.getState().isNew()) {
654                    result.add(resource);
655                }
656            }
657        }
658        return result;
659    }
660
661    /**
662     * Internal method to get the moved folders from the publish list.<p>
663     *
664     * @param cms the current CMS context
665     * @return the list of moved folders from the publish list
666     * @throws CmsException if something goes wrong
667     */
668    protected List<CmsResource> getMovedFolders(CmsObject cms) throws CmsException {
669
670        CmsProject onlineProject = cms.readProject(CmsProject.ONLINE_PROJECT_ID);
671        List<CmsResource> movedFolders = new ArrayList<CmsResource>();
672        for (CmsResource folder : m_folderList) {
673            if (folder.getState().isChanged()) {
674                CmsProject oldProject = cms.getRequestContext().getCurrentProject();
675                boolean isMoved = false;
676                try {
677                    cms.getRequestContext().setCurrentProject(onlineProject);
678                    CmsResource onlineResource = cms.readResource(folder.getStructureId());
679                    isMoved = !onlineResource.getRootPath().equals(folder.getRootPath());
680                } catch (CmsVfsResourceNotFoundException e) {
681                    // resource not found online, this means it doesn't matter whether it has been moved
682                } finally {
683                    cms.getRequestContext().setCurrentProject(oldProject);
684                }
685                if (isMoved) {
686                    movedFolders.add(folder);
687                }
688            }
689        }
690        return movedFolders;
691    }
692
693    /**
694     * Gives the "roots" of a list of folders, i.e. the list of folders which are not descendants of any other folders in the original list
695     * @param folders the original list of folders
696     * @return the root folders of the list
697     */
698    protected List<CmsResource> getTopFolders(List<CmsResource> folders) {
699
700        List<String> folderPaths = new ArrayList<String>();
701        List<CmsResource> topFolders = new ArrayList<CmsResource>();
702        Map<String, CmsResource> foldersByPath = new HashMap<String, CmsResource>();
703        for (CmsResource folder : folders) {
704            folderPaths.add(folder.getRootPath());
705            foldersByPath.put(folder.getRootPath(), folder);
706        }
707        Collections.sort(folderPaths);
708        Set<String> topFolderPaths = new HashSet<String>(folderPaths);
709        for (int i = 0; i < folderPaths.size(); i++) {
710            for (int j = i + 1; j < folderPaths.size(); j++) {
711                if (folderPaths.get(j).startsWith((folderPaths.get(i)))) {
712                    topFolderPaths.remove(folderPaths.get(j));
713                } else {
714                    break;
715                }
716            }
717        }
718        for (String path : topFolderPaths) {
719            topFolders.add(foldersByPath.get(path));
720        }
721        return topFolders;
722    }
723
724    /**
725     * Initializes the publish list, ensuring all internal lists are in the right order.<p>
726     */
727    protected void initialize() {
728
729        if (m_folderList != null) {
730            // ensure folders are sorted starting with parent folders
731            Collections.sort(m_folderList, I_CmsResource.COMPARE_ROOT_PATH);
732        }
733
734        if (m_fileList != null) {
735            // ensure files are sorted starting with files in parent folders
736            Collections.sort(m_fileList, I_CmsResource.COMPARE_ROOT_PATH);
737        }
738
739        if (m_deletedFolderList != null) {
740            // ensure deleted folders are sorted starting with child folders
741            Collections.sort(m_deletedFolderList, I_CmsResource.COMPARE_ROOT_PATH);
742            Collections.reverse(m_deletedFolderList);
743        }
744    }
745
746    /**
747     * Removes a Cms resource from the publish list.<p>
748     *
749     * @param resource a Cms resource
750     *
751     * @return true if this publish list contains the specified resource
752     *
753     * @see List#remove(java.lang.Object)
754     */
755    protected boolean remove(CmsResource resource) {
756
757        // it is essential that this method is only visible within the db package!
758        boolean ret = m_fileList.remove(resource);
759        ret |= m_folderList.remove(resource);
760        ret |= m_deletedFolderList.remove(resource);
761        return ret;
762    }
763
764    /**
765     * Builds a list of <code>CmsResource</code> instances from a list of resource structure IDs.<p>
766     *
767     * @param cms a cms object
768     * @param uuidList the list of structure IDs
769     * @return a list of <code>CmsResource</code> instances
770     */
771    private List<CmsResource> internalReadResourceList(CmsObject cms, List<CmsUUID> uuidList) {
772
773        List<CmsResource> resList = new ArrayList<CmsResource>(uuidList.size());
774        for (Iterator<CmsUUID> i = uuidList.iterator(); i.hasNext();) {
775            try {
776                CmsResource res = cms.readResource(i.next(), CmsResourceFilter.ALL);
777                resList.add(res);
778            } catch (CmsException exc) {
779                LOG.error(exc.getLocalizedMessage(), exc);
780            }
781        }
782
783        return resList;
784    }
785
786    /**
787     * Reads a UUID from an object input.<p>
788     *
789     * @param in the object input
790     * @return a UUID
791     * @throws IOException
792     */
793    private CmsUUID internalReadUUID(ObjectInput in) throws IOException {
794
795        byte[] bytes = new byte[UUID_LENGTH];
796        in.readFully(bytes, 0, UUID_LENGTH);
797        return new CmsUUID(bytes);
798    }
799
800    /**
801     * Reads a sequence of UUIDs from an object input and builds a list of <code>CmsResource</code> instances from it.<p>
802     *
803     * @param in the object input
804     * @return a list of <code>{@link CmsResource}</code> instances
805     *
806     * @throws IOException if something goes wrong
807     */
808    private List<CmsUUID> internalReadUUIDList(ObjectInput in) throws IOException {
809
810        List<CmsUUID> result = null;
811
812        int i = in.readInt();
813        if (i >= 0) {
814            result = new ArrayList<CmsUUID>();
815            while (i > 0) {
816                result.add(internalReadUUID(in));
817                i--;
818            }
819        }
820
821        return result;
822    }
823}