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.relations;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsPropertyDefinition;
032import org.opencms.file.CmsResource;
033import org.opencms.file.CmsResourceFilter;
034import org.opencms.main.CmsException;
035import org.opencms.main.CmsLog;
036import org.opencms.main.OpenCms;
037
038import java.util.ArrayList;
039import java.util.Collection;
040import java.util.Collections;
041import java.util.HashMap;
042import java.util.HashSet;
043import java.util.Iterator;
044import java.util.List;
045import java.util.Map;
046import java.util.Set;
047
048import org.apache.commons.logging.Log;
049
050/**
051 * Util class to find broken links in a bundle of resources to be deleted.<p>
052 *
053 * @since 6.5.3
054 */
055public class CmsRelationDeleteValidator {
056
057    /** The log object for this class. */
058    private static final Log LOG = CmsLog.getLog(CmsRelationDeleteValidator.class);
059
060    /** The internal computed broken relations map. */
061    protected Map<String, List<CmsRelation>> m_brokenRelations;
062
063    /** the cms context object. */
064    private CmsObject m_cms;
065
066    /**
067     * Creates a new helper object.<p>
068     *
069     * @param cms the cms object
070     * @param resourceNames a list of resource names to be deleted
071     * @param includeSiblings if the siblings should also be deleted
072     */
073    public CmsRelationDeleteValidator(CmsObject cms, List<String> resourceNames, boolean includeSiblings) {
074
075        m_cms = cms;
076        m_brokenRelations = getBrokenRelations(resourceNames, includeSiblings);
077    }
078
079    /**
080     * Returns the information bean for the given entry.<p>
081     *
082     * @param resourceName the entry name
083     *
084     * @return the information bean for the given entry
085     */
086    public CmsRelationValidatorInfoEntry getInfoEntry(String resourceName) {
087
088        String resName = resourceName;
089        String siteRoot = m_cms.getRequestContext().getSiteRoot();
090        String siteName = null;
091        if (resName.startsWith(m_cms.getRequestContext().getSiteRoot())) {
092            resName = m_cms.getRequestContext().removeSiteRoot(resName);
093        } else {
094            siteRoot = OpenCms.getSiteManager().getSiteRoot(resName);
095            siteName = siteRoot;
096            if (siteRoot != null) {
097                String oldSite = m_cms.getRequestContext().getSiteRoot();
098                try {
099                    m_cms.getRequestContext().setSiteRoot("/");
100                    siteName = m_cms.readPropertyObject(siteRoot, CmsPropertyDefinition.PROPERTY_TITLE, false).getValue(
101                        siteRoot);
102                } catch (CmsException e) {
103                    siteName = siteRoot;
104                } finally {
105                    m_cms.getRequestContext().setSiteRoot(oldSite);
106                }
107                resName = resName.substring(siteRoot.length());
108            } else {
109                siteName = "/";
110            }
111        }
112        return new CmsRelationValidatorInfoEntry(
113            resourceName,
114            resName,
115            siteName,
116            siteRoot,
117            Collections.unmodifiableList(m_brokenRelations.get(resourceName)));
118    }
119
120    /**
121     * If no relation would be broken deleting the given resources.<p>
122     *
123     * @return <code>true</code> if no relation would be broken deleting the given resources
124     */
125    public boolean isEmpty() {
126
127        return m_brokenRelations.isEmpty();
128    }
129
130    /**
131     * @see java.util.Map#keySet()
132     *
133     * @return the broken relations key set
134     */
135    public Set<String> keySet() {
136
137        return m_brokenRelations.keySet();
138    }
139
140    /**
141     * @see java.util.Map#values()
142     *
143     * @return the broken relations value set
144     */
145    public Collection<List<CmsRelation>> values() {
146
147        return m_brokenRelations.values();
148    }
149
150    /**
151     * Returns a map of where each entry has as key a name of a resource to be deleted,
152     * and value a list of relations that would be broken.<p>
153     *
154     * The keys for non-siblings have following format:
155     * <code>file root path</code>.<p>
156     *
157     * The keys for siblings have following format:
158     * <code>original file root path + PREFIX_SIBLING + sibling root path</code>.<p>
159     *
160     * The values are {@link CmsRelation} objects.<p>
161     *
162     * @param resourceNames a list of resource names to be deleted
163     * @param includeSiblings if the siblings should also be deleted
164     *
165     * @return a map of broken relations
166     */
167    private Map<String, List<CmsRelation>> getBrokenRelations(List<String> resourceNames, boolean includeSiblings) {
168
169        Map<String, List<CmsRelation>> brokenRelations = new HashMap<String, List<CmsRelation>>();
170        Set<String> resources = new HashSet<String>();
171        // expand the folders to single resources
172        String site = m_cms.getRequestContext().getSiteRoot();
173        String oldSite = site;
174        try {
175            m_cms.getRequestContext().setSiteRoot("/");
176            List<CmsResource> resourceList = new ArrayList<CmsResource>();
177            Iterator<String> itResourceNames = resourceNames.iterator();
178            while (itResourceNames.hasNext()) {
179                // get the root path
180                String resName = m_cms.getRequestContext().addSiteRoot(site, itResourceNames.next());
181                try {
182                    CmsResource resource = m_cms.readResource(resName);
183                    resourceList.add(resource);
184                    if (resource.isFolder()) {
185                        resourceList.addAll(m_cms.readResources(resName, CmsResourceFilter.IGNORE_EXPIRATION, true));
186                    }
187                } catch (CmsException e) {
188                    // should never happen
189                    if (LOG.isDebugEnabled()) {
190                        LOG.debug(e.getLocalizedMessage(), e);
191                    }
192                }
193            }
194
195            // collect the root paths
196            Iterator<CmsResource> itResources = resourceList.iterator();
197            while (itResources.hasNext()) {
198                CmsResource resource = itResources.next();
199                resources.add(resource.getRootPath());
200            }
201
202            if (Boolean.valueOf(includeSiblings).booleanValue()) {
203                // expand the siblings
204                itResources = new ArrayList<CmsResource>(resourceList).iterator();
205                while (itResources.hasNext()) {
206                    CmsResource resource = itResources.next();
207                    try {
208                        if (!resource.isFolder() && (resource.getSiblingCount() > 1)) {
209                            Iterator<CmsResource> itSiblings = m_cms.readSiblings(
210                                resource.getRootPath(),
211                                CmsResourceFilter.IGNORE_EXPIRATION).iterator();
212                            while (itSiblings.hasNext()) {
213                                CmsResource sibling = itSiblings.next();
214                                if (!resources.contains(sibling.getRootPath())) {
215                                    resources.add(sibling.getRootPath());
216                                    resourceList.add(sibling);
217                                }
218                            }
219                        }
220                    } catch (CmsException e) {
221                        // should never happen
222                        if (LOG.isErrorEnabled()) {
223                            LOG.error(e.getLocalizedMessage(), e);
224                        }
225                    }
226                }
227            }
228
229            // check every resource
230            itResources = resourceList.iterator();
231            while (itResources.hasNext()) {
232                CmsResource resource = itResources.next();
233                String resourceName = resource.getRootPath();
234                try {
235                    Iterator<CmsRelation> it = m_cms.getRelationsForResource(
236                        resource,
237                        CmsRelationFilter.SOURCES).iterator();
238                    while (it.hasNext()) {
239                        CmsRelation relation = it.next();
240                        String relationName = relation.getSourcePath();
241                        // add only if the source is not to be deleted too
242                        if (!resources.contains(relationName)) {
243                            List<CmsRelation> broken = brokenRelations.get(resourceName);
244                            if (broken == null) {
245                                broken = new ArrayList<CmsRelation>();
246                                brokenRelations.put(resourceName, broken);
247                            }
248                            broken.add(relation);
249                        }
250                    }
251                } catch (CmsException e) {
252                    // should never happen
253                    if (LOG.isErrorEnabled()) {
254                        LOG.error(e.getLocalizedMessage(), e);
255                    }
256                }
257            }
258        } finally {
259            m_cms.getRequestContext().setSiteRoot(oldSite);
260        }
261        return brokenRelations;
262    }
263}