org.opencms.db.CmsPublishList.java Source code

Java tutorial

Introduction

Here is the source code for org.opencms.db.CmsPublishList.java

Source

/*
 * This library is part of OpenCms -
 * the Open Source Content Management System
 *
 * Copyright (c) Alkacon Software GmbH (http://www.alkacon.com)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * For further information about Alkacon Software GmbH, please see the
 * company website: http://www.alkacon.com
 *
 * For further information about OpenCms, please see the
 * project website: http://www.opencms.org
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.opencms.db;

import org.opencms.file.CmsObject;
import org.opencms.file.CmsProject;
import org.opencms.file.CmsResource;
import org.opencms.file.CmsResourceFilter;
import org.opencms.file.CmsVfsResourceNotFoundException;
import org.opencms.file.I_CmsResource;
import org.opencms.main.CmsException;
import org.opencms.main.CmsIllegalArgumentException;
import org.opencms.main.CmsLog;
import org.opencms.util.CmsFileUtil;
import org.opencms.util.CmsUUID;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.logging.Log;

/**
 * A container for all new/changed/deteled Cms resources that are published together.<p>
 * 
 * Only classes inside the org.opencms.db package can add or remove elements to or from this list. 
 * This allows the OpenCms API to pass the list around between classes, but with restricted access to 
 * create this list.<p>
 * 
 * To create a publish list, one of the public constructors must be used in order to set the basic operation mode
 * (project publish or direct publish).
 * After this, use <code>{@link org.opencms.db.CmsDriverManager#fillPublishList(CmsDbContext, CmsPublishList)}</code>
 * to fill the actual values of the publish list.<p>
 * 
 * @since 6.0.0
 * 
 * @see org.opencms.db.CmsDriverManager#fillPublishList(CmsDbContext, CmsPublishList)
 */
public class CmsPublishList implements Externalizable {

    /** The log object for this class. */
    private static final Log LOG = CmsLog.getLog(CmsPublishList.class);

    /** Indicates a non existent object in the serialized data. */
    private static final int NIL = -1;

    /** Serial version UID required for safe serialization. */
    private static final long serialVersionUID = -2578909250462750927L;

    /** Length of a serialized uuid. */
    private static final int UUID_LENGTH = CmsUUID.getNullUUID().toByteArray().length;

    /** The list of deleted folder resources to be published.<p> */
    private List<CmsResource> m_deletedFolderList;

    /** The list of deleted folder UUIDs to be published for later retrieval.<p> */
    private List<CmsUUID> m_deletedFolderUUIDs;

    /** The list of direct publish resources. */
    private List<CmsResource> m_directPublishResources;

    /** The list of direct publish resource UUIDs to be published for later retrieval.<p> */
    private List<CmsUUID> m_directPublishResourceUUIDs;

    /** The list of new/changed/deleted file resources to be published.<p> */
    private List<CmsResource> m_fileList;

    /** The list of new/changed/deleted file resource UUIDs to be published for later retrieval.<p> */
    private List<CmsUUID> m_fileUUIDs;

    /** The list of new/changed folder resources to be published.<p> */
    private List<CmsResource> m_folderList;

    /** The list of new/changed folder resource UUIDs to be published for later retrieval.<p> */
    private List<CmsUUID> m_folderUUIDs;

    /** Indicates whether this is a user publish list. */
    private boolean m_isUserPublishList;

    /** Flag to indicate if the list needs to be revived. */
    private boolean m_needsRevive;

    /** The id of the project that is to be published. */
    private CmsUUID m_projectId;

    /** The publish history ID.<p> */
    private CmsUUID m_publishHistoryId;

    /** Indicates if siblings of the resources in the list should also be published. */
    private boolean m_publishSiblings;

    /** Indicates if sub-resources in folders should be published (for direct publish only). */
    private boolean m_publishSubResources;

    /**
     * Empty constructor.<p> 
     */
    public CmsPublishList() {

        // noop
    }

    /**
     * Constructs a publish list for a list of direct publish resources.<p>
     * 
     * @param all no redundant resource are filtered out
     * @param directPublishResources a list of <code>{@link CmsResource}</code> instances to be published directly
     * @param directPublishSiblings indicates if all siblings of the selected resources should be published
     */
    public CmsPublishList(boolean all, List<CmsResource> directPublishResources, boolean directPublishSiblings) {

        this(null, directPublishResources, directPublishSiblings, false, true);
    }

    /**
     * Constructs a publish list for a given project.<p>
     * 
     * @param project the project to publish, this should always be the id of the current project
     */
    public CmsPublishList(CmsProject project) {

        this(project, null, false, true, false);
    }

    /**
     * Constructs a publish list for a single direct publish resource.<p>
     * 
     * @param directPublishResource a VFS resource to be published directly
     * @param publishSiblings indicates if all siblings of the selected resources should be published
     */
    public CmsPublishList(CmsResource directPublishResource, boolean publishSiblings) {

        this(null, Collections.singletonList(directPublishResource), publishSiblings, true, false);
    }

    /**
     * Constructs a publish list for a list of direct publish resources.<p>
     * 
     * @param directPublishResources a list of <code>{@link CmsResource}</code> instances to be published directly
     * @param publishSiblings indicates if all siblings of the selected resources should be published
     */
    public CmsPublishList(List<CmsResource> directPublishResources, boolean publishSiblings) {

        this(null, directPublishResources, publishSiblings, true, false);
    }

    /**
     * Constructs a publish list for a list of direct publish resources.<p>
     * 
     * @param directPublishResources a list of <code>{@link CmsResource}</code> instances to be published directly
     * @param publishSiblings indicates if all siblings of the selected resources should be published
     * @param publishSubResources indicates if sub-resources in folders should be published (for direct publish only)
     */
    public CmsPublishList(List<CmsResource> directPublishResources, boolean publishSiblings,
            boolean publishSubResources) {

        this(null, directPublishResources, publishSiblings, publishSubResources, false);
    }

    /**
     * Internal constructor for a publish list.<p>
     * 
     * @param project the project to publish
     * @param directPublishResources the list of direct publish resources
     * @param publishSiblings indicates if all siblings of the selected resources should be published
     * @param publishSubResources indicates if sub-resources in folders should be published (for direct publish only)
     */
    private CmsPublishList(CmsProject project, List<CmsResource> directPublishResources, boolean publishSiblings,
            boolean publishSubResources, boolean all) {

        m_fileList = new ArrayList<CmsResource>();
        m_folderList = new ArrayList<CmsResource>();
        m_deletedFolderList = new ArrayList<CmsResource>();
        m_publishHistoryId = new CmsUUID();
        m_publishSiblings = publishSiblings;
        m_publishSubResources = publishSubResources;
        m_projectId = (project != null) ? project.getUuid() : null;
        if (directPublishResources != null) {
            if (!all) {
                // reduce list of folders to minimum
                m_directPublishResources = Collections
                        .unmodifiableList(CmsFileUtil.removeRedundantResources(directPublishResources));
            } else {
                m_directPublishResources = Collections.unmodifiableList(directPublishResources);
            }
        }
    }

    /**
     * Returns a list of all resources in the publish list, 
     * including folders and files.<p>
     * 
     * @return a list of {@link CmsResource} objects
     */
    public List<CmsResource> getAllResources() {

        List<CmsResource> all = new ArrayList<CmsResource>();
        all.addAll(m_folderList);
        all.addAll(m_fileList);
        all.addAll(m_deletedFolderList);

        Collections.sort(all, I_CmsResource.COMPARE_ROOT_PATH);
        return Collections.unmodifiableList(all);
    }

    /**
     * Returns a list of folder resources with the deleted state.<p>
     * 
     * @return a list of folder resources with the deleted state
     */
    public List<CmsResource> getDeletedFolderList() {

        if (m_needsRevive) {
            return null;
        } else {
            return m_deletedFolderList;
        }
    }

    /**
     * Returns the list of resources that should be published for a "direct" publish operation.<p>
     * 
     * Will return <code>null</code> if this publish list was not initialized for a "direct publish" but
     * for a project publish.<p>
     * 
     * @return the list of resources that should be published for a "direct" publish operation, or <code>null</code>
     */
    public List<CmsResource> getDirectPublishResources() {

        if (m_needsRevive) {
            return null;
        } else {
            return m_directPublishResources;
        }
    }

    /**
     * Returns an unmodifiable list of the files in this publish list.<p>
     * 
     * @return the list with the files in this publish list
     */
    public List<CmsResource> getFileList() {

        if (m_needsRevive) {
            return null;
        } else {
            return Collections.unmodifiableList(m_fileList);
        }
    }

    /**
     * Returns an unmodifiable list of the new/changed folders in this publish list.<p>
     * 
     * @return the list with the new/changed folders in this publish list
     */
    public List<CmsResource> getFolderList() {

        if (m_needsRevive) {
            return null;
        } else {
            return Collections.unmodifiableList(m_folderList);
        }
    }

    /**
     * Returns the id of the project that should be published, or <code>-1</code> if this publish list
     * is initialized for a "direct publish" operation.<p>
     * 
     * @return the id of the project that should be published, or <code>-1</code>
     */
    public CmsUUID getProjectId() {

        return m_projectId;
    }

    /**
     * Returns the publish history Id for this publish list.<p>
     * 
     * @return the publish history Id
     */
    public CmsUUID getPublishHistoryId() {

        return m_publishHistoryId;
    }

    /**
     * Gets the list of moved folders which are not subfolders of other moved folders in the publish list.<p>
     * @param cms the current cms context 
     * @return the moved folders which are not subfolders of other moved folders in the publish list  
     * @throws CmsException if something goes wrong 
     */
    public List<CmsResource> getTopMovedFolders(CmsObject cms) throws CmsException {

        List<CmsResource> movedFolders = getMovedFolders(cms);
        List<CmsResource> result = getTopFolders(movedFolders);
        return result;
    }

    /**
     * Checks if this is a publish list is used for a "direct publish" operation.<p>
     * 
     * @return true if this is a publish list is used for a "direct publish" operation
     */
    public boolean isDirectPublish() {

        return (m_projectId == null);
    }

    /**
     * Returns <code>true</code> if all siblings of the project resources are to be published.<p>
     *  
     * @return <code>true</code> if all siblings of the project resources are to be publisheds
     */
    public boolean isPublishSiblings() {

        return m_publishSiblings;
    }

    /**
     * Returns <code>true</code> if sub-resources in folders should be published (for direct publish only).<p>
     * 
     * @return <code>true</code> if sub-resources in folders should be published (for direct publish only)
     */
    public boolean isPublishSubResources() {

        return m_publishSubResources;
    }

    /**
     * Returns true if this is a user publish list.<p>
     * 
     * @return true if this is a user publish list 
     */
    public boolean isUserPublishList() {

        return m_isUserPublishList;
    }

    /**
     * @see java.io.Externalizable#readExternal(java.io.ObjectInput)
     */
    public void readExternal(ObjectInput in) throws IOException {

        // read the history id
        m_publishHistoryId = internalReadUUID(in);
        // read the project id
        m_projectId = internalReadUUID(in);
        if (m_projectId.isNullUUID()) {
            m_projectId = null;
        }
        // read the flags
        m_publishSiblings = (in.readInt() != 0);
        m_publishSubResources = (in.readInt() != 0);
        // read the list of direct published resources
        m_directPublishResourceUUIDs = internalReadUUIDList(in);
        // read the list of published files
        m_fileUUIDs = internalReadUUIDList(in);
        // read the list of published folders
        m_folderUUIDs = internalReadUUIDList(in);
        // read the list of deleted folders
        m_deletedFolderUUIDs = internalReadUUIDList(in);
        // set revive flag to indicate that resource lists must be revived
        m_needsRevive = true;
    }

    /**
     * Revives the publish list by populating the internal resource lists with <code>{@link CmsResource}</code> instances.<p>
     * 
     * @param cms a cms object used to read the resource instances
     */
    public void revive(CmsObject cms) {

        if (m_needsRevive) {
            if (m_directPublishResourceUUIDs != null) {
                m_directPublishResources = internalReadResourceList(cms, m_directPublishResourceUUIDs);
            }
            if (m_fileUUIDs != null) {
                m_fileList = internalReadResourceList(cms, m_fileUUIDs);
            }
            if (m_folderUUIDs != null) {
                m_folderList = internalReadResourceList(cms, m_folderUUIDs);
            }
            if (m_deletedFolderUUIDs != null) {
                m_deletedFolderList = internalReadResourceList(cms, m_deletedFolderUUIDs);
            }
            m_needsRevive = false;
        }
    }

    /**
     * Sets the 'user publish list' flag on this publish list.<p>
     * 
     * @param isUserPublishList if true, the list is marked as a user publish list
     */
    public void setUserPublishList(boolean isUserPublishList) {

        m_isUserPublishList = isUserPublishList;
    }

    /**
     * Returns the number of all resources to be published.<p>
     * 
     * @return the number of all resources to be published
     */
    public int size() {

        if (m_needsRevive) {
            return 0;
        } else {
            return m_folderList.size() + m_fileList.size() + m_deletedFolderList.size();
        }
    }

    /**
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {

        StringBuffer result = new StringBuffer();
        result.append("\n[\n");
        if (isDirectPublish()) {
            result.append("direct publish of resources: ").append(m_directPublishResources.toString()).append("\n");
        } else {
            result.append("publish of project: ").append(m_projectId).append("\n");
        }
        result.append("publish history ID: ").append(m_publishHistoryId.toString()).append("\n");
        result.append("resources: ").append(m_fileList.toString()).append("\n");
        result.append("folders: ").append(m_folderList.toString()).append("\n");
        result.append("deletedFolders: ").append(m_deletedFolderList.toString()).append("\n");
        result.append("]\n");
        return result.toString();
    }

    /**
     * @see java.io.Externalizable#writeExternal(java.io.ObjectOutput)
     */
    public void writeExternal(ObjectOutput out) throws IOException {

        // write the history id
        out.write(m_publishHistoryId.toByteArray());
        // write the project id
        out.write((m_projectId != null) ? m_projectId.toByteArray() : CmsUUID.getNullUUID().toByteArray());
        // write the flags
        out.writeInt((m_publishSiblings) ? 1 : 0);
        out.writeInt((m_publishSubResources) ? 1 : 0);
        // write the list of direct publish resources by writing the uuid of each resource
        if (m_directPublishResources != null) {
            out.writeInt(m_directPublishResources.size());
            for (Iterator<CmsResource> i = m_directPublishResources.iterator(); i.hasNext();) {
                out.write((i.next()).getStructureId().toByteArray());
            }
        } else {
            out.writeInt(NIL);
        }
        // write the list of published files by writing the uuid of each resource
        if (m_fileList != null) {
            out.writeInt(m_fileList.size());
            for (Iterator<CmsResource> i = m_fileList.iterator(); i.hasNext();) {
                out.write((i.next()).getStructureId().toByteArray());
            }
        } else {
            out.writeInt(NIL);
        }
        // write the list of published folders by writing the uuid of each resource
        if (m_folderList != null) {
            out.writeInt(m_folderList.size());
            for (Iterator<CmsResource> i = m_folderList.iterator(); i.hasNext();) {
                out.write((i.next()).getStructureId().toByteArray());
            }
        } else {
            out.writeInt(NIL);
        }
        // write the list of deleted folders by writing the uuid of each resource
        if (m_deletedFolderList != null) {
            out.writeInt(m_deletedFolderList.size());
            for (Iterator<CmsResource> i = m_deletedFolderList.iterator(); i.hasNext();) {
                out.write((i.next()).getStructureId().toByteArray());
            }
        } else {
            out.writeInt(NIL);
        }
    }

    /**
     * Adds a new/changed Cms folder resource to the publish list.<p>
     * 
     * @param resource a new/changed Cms folder resource
     * @param check if set an exception is thrown if the specified resource is unchanged, 
     *              if not set the resource is ignored
     * 
     * @throws IllegalArgumentException if the specified resource is unchanged
     */
    protected void add(CmsResource resource, boolean check) throws IllegalArgumentException {

        if (check) {
            // it is essential that this method is only visible within the db package!
            if (resource.getState().isUnchanged()) {
                throw new CmsIllegalArgumentException(Messages.get()
                        .container(Messages.ERR_PUBLISH_UNCHANGED_RESOURCE_1, resource.getRootPath()));
            }
        }
        if (resource.isFolder()) {
            if (resource.getState().isDeleted()) {
                if (!m_deletedFolderList.contains(resource)) {
                    // only add files not already contained in the list
                    m_deletedFolderList.add(resource);
                }
            } else {
                if (!m_folderList.contains(resource)) {
                    // only add files not already contained in the list
                    m_folderList.add(resource);
                }
            }
        } else {
            if (!m_fileList.contains(resource)) {
                // only add files not already contained in the list
                // this is required to make sure no siblings are duplicated
                m_fileList.add(resource);
            }
        }
    }

    /**
     * Appends all the given resources to this publish list.<p>
     * 
     * @param resources resources to be added to this publish list
     * @param check if set an exception is thrown if the a resource is unchanged, 
     *              if not set the resource is ignored
     * 
     * @throws IllegalArgumentException if one of the resources is unchanged
     */
    protected void addAll(Collection<CmsResource> resources, boolean check) throws IllegalArgumentException {

        // it is essential that this method is only visible within the db package!
        Iterator<CmsResource> i = resources.iterator();
        while (i.hasNext()) {
            add(i.next(), check);
        }
    }

    /**
     * Checks whether the publish list contains all sub-resources of a list of folders.<p>
     * 
     * @param cms the current CMS context 
     * @param folders the folders which should be checked 
     * @return a folder from the list if one of its sub-resources is not contained in the publish list, otherwise null
     * 
     * @throws CmsException if something goes wrong 
     */
    protected CmsResource checkContainsSubResources(CmsObject cms, List<CmsResource> folders) throws CmsException {

        for (CmsResource folder : folders) {

            if (!containsSubResources(cms, folder)) {
                return folder;
            }
        }
        return null;
    }

    /**
     * Checks if the publish list contains a resource.<p>
     * 
     * @param res the resource
     * @return true if the publish list contains a resource 
     */
    protected boolean containsResource(CmsResource res) {

        return m_deletedFolderList.contains(res) || m_folderList.contains(res) || m_fileList.contains(res);
    }

    /**
     * Checks if the publish list contains all sub-resources of a given folder.<p>
     * 
     * @param cms the current CMS context 
     * @param folder the folder for which the check should be performed 
     * @return true if the publish list contains all sub-resources of a given folder 
     * @throws CmsException if something goes wrong 
     */
    protected boolean containsSubResources(CmsObject cms, CmsResource folder) throws CmsException {

        String folderPath = cms.getSitePath(folder);
        List<CmsResource> subResources = cms.readResources(folderPath, CmsResourceFilter.ALL, true);
        for (CmsResource resource : subResources) {
            if (!containsResource(resource)) {
                return false;
            }
        }
        return true;
    }

    /**
     * Internal method to get the moved folders from the publish list.<p>
     * 
     * @param cms the current CMS context 
     * @return the list of moved folders from the publish list 
     * @throws CmsException if something goes wrong 
     */
    protected List<CmsResource> getMovedFolders(CmsObject cms) throws CmsException {

        CmsProject onlineProject = cms.readProject(CmsProject.ONLINE_PROJECT_ID);
        List<CmsResource> movedFolders = new ArrayList<CmsResource>();
        for (CmsResource folder : m_folderList) {
            if (folder.getState().isChanged()) {
                CmsProject oldProject = cms.getRequestContext().getCurrentProject();
                boolean isMoved = false;
                try {
                    cms.getRequestContext().setCurrentProject(onlineProject);
                    CmsResource onlineResource = cms.readResource(folder.getStructureId());
                    isMoved = !onlineResource.getRootPath().equals(folder.getRootPath());
                } catch (CmsVfsResourceNotFoundException e) {
                    // resource not found online, this means it doesn't matter whether it has been moved 
                } finally {
                    cms.getRequestContext().setCurrentProject(oldProject);
                }
                if (isMoved) {
                    movedFolders.add(folder);
                }
            }
        }
        return movedFolders;
    }

    /**
     * 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 
     * @param folders the original list of folders 
     * @return the root folders of the list 
     */
    protected List<CmsResource> getTopFolders(List<CmsResource> folders) {

        List<String> folderPaths = new ArrayList<String>();
        List<CmsResource> topFolders = new ArrayList<CmsResource>();
        Map<String, CmsResource> foldersByPath = new HashMap<String, CmsResource>();
        for (CmsResource folder : folders) {
            folderPaths.add(folder.getRootPath());
            foldersByPath.put(folder.getRootPath(), folder);
        }
        Collections.sort(folderPaths);
        Set<String> topFolderPaths = new HashSet<String>(folderPaths);
        for (int i = 0; i < folderPaths.size(); i++) {
            for (int j = i + 1; j < folderPaths.size(); j++) {
                if (folderPaths.get(j).startsWith((folderPaths.get(i)))) {
                    topFolderPaths.remove(folderPaths.get(j));
                } else {
                    break;
                }
            }
        }
        for (String path : topFolderPaths) {
            topFolders.add(foldersByPath.get(path));
        }
        return topFolders;
    }

    /**
     * Initializes the publish list, ensuring all internal lists are in the right order.<p>
     */
    protected void initialize() {

        if (m_folderList != null) {
            // ensure folders are sorted starting with parent folders
            Collections.sort(m_folderList, I_CmsResource.COMPARE_ROOT_PATH);
        }

        if (m_fileList != null) {
            // ensure files are sorted starting with files in parent folders
            Collections.sort(m_fileList, I_CmsResource.COMPARE_ROOT_PATH);
        }

        if (m_deletedFolderList != null) {
            // ensure deleted folders are sorted starting with child folders
            Collections.sort(m_deletedFolderList, I_CmsResource.COMPARE_ROOT_PATH);
            Collections.reverse(m_deletedFolderList);
        }
    }

    /**
     * Removes a Cms resource from the publish list.<p>
     * 
     * @param resource a Cms resource
     * 
     * @return true if this publish list contains the specified resource
     * 
     * @see List#remove(java.lang.Object)
     */
    protected boolean remove(CmsResource resource) {

        // it is essential that this method is only visible within the db package!
        boolean ret = m_fileList.remove(resource);
        ret |= m_folderList.remove(resource);
        ret |= m_deletedFolderList.remove(resource);
        return ret;
    }

    /**
     * Builds a list of <code>CmsResource</code> instances from a list of resource structure IDs.<p>
     * 
     * @param cms a cms object
     * @param uuidList the list of structure IDs
     * @return a list of <code>CmsResource</code> instances
     */
    private List<CmsResource> internalReadResourceList(CmsObject cms, List<CmsUUID> uuidList) {

        List<CmsResource> resList = new ArrayList<CmsResource>(uuidList.size());
        for (Iterator<CmsUUID> i = uuidList.iterator(); i.hasNext();) {
            try {
                CmsResource res = cms.readResource(i.next(), CmsResourceFilter.ALL);
                resList.add(res);
            } catch (CmsException exc) {
                LOG.error(exc.getLocalizedMessage(), exc);
            }
        }

        return resList;
    }

    /**
     * Reads a UUID from an object input.<p>
     * 
     * @param in the object input
     * @return a UUID
     * @throws IOException
     */
    private CmsUUID internalReadUUID(ObjectInput in) throws IOException {

        byte[] bytes = new byte[UUID_LENGTH];
        in.readFully(bytes, 0, UUID_LENGTH);
        return new CmsUUID(bytes);
    }

    /**
     * Reads a sequence of UUIDs from an object input and builds a list of <code>CmsResource</code> instances from it.<p>
     * 
     * @param in the object input
     * @return a list of <code>{@link CmsResource}</code> instances
     * 
     * @throws IOException if something goes wrong 
     */
    private List<CmsUUID> internalReadUUIDList(ObjectInput in) throws IOException {

        List<CmsUUID> result = null;

        int i = in.readInt();
        if (i >= 0) {
            result = new ArrayList<CmsUUID>();
            while (i > 0) {
                result.add(internalReadUUID(in));
                i--;
            }
        }

        return result;
    }
}