org.opencms.db.CmsPublishList Maven / Gradle / Ivy
Show all versions of opencms-test Show documentation
/*
* This library is part of OpenCms -
* the Open Source Content Management System
*
* Copyright (c) Alkacon Software GmbH & Co. KG (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 & Co. KG, 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.
*
* 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.
*
* 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 {@link org.opencms.db.CmsDriverManager#fillPublishList(CmsDbContext, CmsPublishList)}
* to fill the actual values of the publish list.
*
* @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.
*/
private List m_deletedFolderList;
/** The list of deleted folder UUIDs to be published for later retrieval. */
private List m_deletedFolderUUIDs;
/** The list of direct publish resources. */
private List m_directPublishResources;
/** The list of direct publish resource UUIDs to be published for later retrieval. */
private List m_directPublishResourceUUIDs;
/** The list of new/changed/deleted file resources to be published. */
private List m_fileList;
/** The list of new/changed/deleted file resource UUIDs to be published for later retrieval. */
private List m_fileUUIDs;
/** The list of new/changed folder resources to be published. */
private List m_folderList;
/** The list of new/changed folder resource UUIDs to be published for later retrieval. */
private List 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. */
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.
*/
public CmsPublishList() {
// noop
}
/**
* Constructs a publish list for a list of direct publish resources.
*
* @param all no redundant resource are filtered out
* @param directPublishResources a list of {@link CmsResource}
instances to be published directly
* @param directPublishSiblings indicates if all siblings of the selected resources should be published
*/
public CmsPublishList(boolean all, List directPublishResources, boolean directPublishSiblings) {
this(null, directPublishResources, directPublishSiblings, false, true);
}
/**
* Constructs a publish list for a given project.
*
* @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.
*
* @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.
*
* @param directPublishResources a list of {@link CmsResource}
instances to be published directly
* @param publishSiblings indicates if all siblings of the selected resources should be published
*/
public CmsPublishList(List directPublishResources, boolean publishSiblings) {
this(null, directPublishResources, publishSiblings, true, false);
}
/**
* Constructs a publish list for a list of direct publish resources.
*
* @param directPublishResources a list of {@link CmsResource}
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 directPublishResources,
boolean publishSiblings,
boolean publishSubResources) {
this(null, directPublishResources, publishSiblings, publishSubResources, false);
}
/**
* Internal constructor for a publish list.
*
* @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)
* @param all if true
the publish list will not be filtered for redundant resources
*/
private CmsPublishList(
CmsProject project,
List directPublishResources,
boolean publishSiblings,
boolean publishSubResources,
boolean all) {
m_fileList = new ArrayList();
m_folderList = new ArrayList();
m_deletedFolderList = new ArrayList();
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.
*
* @return a list of {@link CmsResource} objects
*/
public List getAllResources() {
List all = new ArrayList();
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.
*
* @return a list of folder resources with the deleted state
*/
public List getDeletedFolderList() {
if (m_needsRevive) {
return null;
} else {
return m_deletedFolderList;
}
}
/**
* Returns the list of resources that should be published for a "direct" publish operation.
*
* Will return null
if this publish list was not initialized for a "direct publish" but
* for a project publish.
*
* @return the list of resources that should be published for a "direct" publish operation, or null
*/
public List getDirectPublishResources() {
if (m_needsRevive) {
return null;
} else {
return m_directPublishResources;
}
}
/**
* Returns an unmodifiable list of the files in this publish list.
*
* @return the list with the files in this publish list
*/
public List 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.
*
* @return the list with the new/changed folders in this publish list
*/
public List getFolderList() {
if (m_needsRevive) {
return null;
} else {
return Collections.unmodifiableList(m_folderList);
}
}
/**
* Returns the id of the project that should be published, or -1
if this publish list
* is initialized for a "direct publish" operation.
*
* @return the id of the project that should be published, or -1
*/
public CmsUUID getProjectId() {
return m_projectId;
}
/**
* Returns the publish history Id for this publish list.
*
* @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.
* @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 getTopMovedFolders(CmsObject cms) throws CmsException {
List movedFolders = getMovedFolders(cms);
List result = getTopFolders(movedFolders);
return result;
}
/**
* Checks if this is a publish list is used for a "direct publish" operation.
*
* @return true if this is a publish list is used for a "direct publish" operation
*/
public boolean isDirectPublish() {
return (m_projectId == null);
}
/**
* Returns true
if all siblings of the project resources are to be published.
*
* @return true
if all siblings of the project resources are to be publisheds
*/
public boolean isPublishSiblings() {
return m_publishSiblings;
}
/**
* Returns true
if sub-resources in folders should be published (for direct publish only).
*
* @return true
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.
*
* @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 {@link CmsResource}
instances.
*
* @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.
*
* @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.
*
* @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 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 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 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 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.
*
* @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.
*
* @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 resources, boolean check) throws IllegalArgumentException {
// it is essential that this method is only visible within the db package!
Iterator i = resources.iterator();
while (i.hasNext()) {
add(i.next(), check);
}
}
/**
* Checks whether the publish list contains all sub-resources of a list of folders.
*
* @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 folders) throws CmsException {
for (CmsResource folder : folders) {
if (!containsSubResources(cms, folder)) {
return folder;
}
}
return null;
}
/**
* Checks if the publish list contains a resource.
*
* @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.
*
* @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 subResources = cms.readResources(folderPath, CmsResourceFilter.ALL, true);
for (CmsResource resource : subResources) {
if (!containsResource(resource)) {
return false;
}
}
return true;
}
/**
* Gets the sub-resources of a list of folders which are missing from the publish list.
*
* @param cms the current CMS context
* @param folders the folders which should be checked
* @return a list of missing sub resources
*
* @throws CmsException if something goes wrong
*/
protected List getMissingSubResources(CmsObject cms, List folders) throws CmsException {
List result = new ArrayList();
for (CmsResource folder : folders) {
String folderPath = cms.getSitePath(folder);
List subResources = cms.readResources(folderPath, CmsResourceFilter.ALL, true);
for (CmsResource resource : subResources) {
if (!containsResource(resource)) {
result.add(resource);
}
}
}
return result;
}
/**
* Internal method to get the moved folders from the publish list.
*
* @param cms the current CMS context
* @return the list of moved folders from the publish list
* @throws CmsException if something goes wrong
*/
protected List getMovedFolders(CmsObject cms) throws CmsException {
CmsProject onlineProject = cms.readProject(CmsProject.ONLINE_PROJECT_ID);
List movedFolders = new ArrayList();
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 getTopFolders(List folders) {
List folderPaths = new ArrayList();
List topFolders = new ArrayList();
Map foldersByPath = new HashMap();
for (CmsResource folder : folders) {
folderPaths.add(folder.getRootPath());
foldersByPath.put(folder.getRootPath(), folder);
}
Collections.sort(folderPaths);
Set topFolderPaths = new HashSet(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.
*/
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.
*
* @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 CmsResource
instances from a list of resource structure IDs.
*
* @param cms a cms object
* @param uuidList the list of structure IDs
* @return a list of CmsResource
instances
*/
private List internalReadResourceList(CmsObject cms, List uuidList) {
List resList = new ArrayList(uuidList.size());
for (Iterator 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.
*
* @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 CmsResource
instances from it.
*
* @param in the object input
* @return a list of {@link CmsResource}
instances
*
* @throws IOException if something goes wrong
*/
private List internalReadUUIDList(ObjectInput in) throws IOException {
List result = null;
int i = in.readInt();
if (i >= 0) {
result = new ArrayList();
while (i > 0) {
result.add(internalReadUUID(in));
i--;
}
}
return result;
}
}