org.opencms.relations.CmsCategoryService 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.relations;
import org.opencms.file.CmsObject;
import org.opencms.file.CmsProperty;
import org.opencms.file.CmsPropertyDefinition;
import org.opencms.file.CmsResource;
import org.opencms.file.CmsResourceFilter;
import org.opencms.file.CmsVfsResourceNotFoundException;
import org.opencms.file.types.CmsResourceTypeFolder;
import org.opencms.lock.CmsLock;
import org.opencms.main.CmsException;
import org.opencms.main.CmsLog;
import org.opencms.main.OpenCms;
import org.opencms.util.CmsStringUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
/**
* Provides several simplified methods for manipulating category relations.
*
* @since 6.9.2
*
* @see CmsCategory
*/
public class CmsCategoryService {
/** The centralized path for categories. */
public static final String CENTRALIZED_REPOSITORY = "/system/categories/";
/** The folder for the local category repositories. */
public static final String REPOSITORY_BASE_FOLDER = "/.categories/";
/** The log object for this class. */
private static final Log LOG = CmsLog.getLog(CmsCategoryService.class);
/** The singleton instance. */
private static CmsCategoryService m_instance;
/**
* Returns the singleton instance.
*
* @return the singleton instance
*/
public static CmsCategoryService getInstance() {
if (m_instance == null) {
m_instance = new CmsCategoryService();
}
return m_instance;
}
/**
* Adds a resource identified by the given resource name to the given category.
*
* The resource has to be locked.
*
* @param cms the current cms context
* @param resourceName the site relative path to the resource to add
* @param category the category to add the resource to
*
* @throws CmsException if something goes wrong
*/
public void addResourceToCategory(CmsObject cms, String resourceName, CmsCategory category) throws CmsException {
if (readResourceCategories(cms, cms.readResource(resourceName, CmsResourceFilter.IGNORE_EXPIRATION)).contains(
category)) {
return;
}
String sitePath = cms.getRequestContext().removeSiteRoot(category.getRootPath());
cms.addRelationToResource(resourceName, sitePath, CmsRelationType.CATEGORY.getName());
String parentCatPath = category.getPath();
// recursively add to higher level categories
if (parentCatPath.endsWith("/")) {
parentCatPath = parentCatPath.substring(0, parentCatPath.length() - 1);
}
if (parentCatPath.lastIndexOf('/') > 0) {
addResourceToCategory(cms, resourceName, parentCatPath.substring(0, parentCatPath.lastIndexOf('/') + 1));
}
}
/**
* Adds a resource identified by the given resource name to the category
* identified by the given category path.
*
* Only the most global category matching the given category path for the
* given resource will be affected.
*
* The resource has to be locked.
*
* @param cms the current cms context
* @param resourceName the site relative path to the resource to add
* @param categoryPath the path of the category to add the resource to
*
* @throws CmsException if something goes wrong
*/
public void addResourceToCategory(CmsObject cms, String resourceName, String categoryPath) throws CmsException {
CmsCategory category = readCategory(cms, categoryPath, resourceName);
addResourceToCategory(cms, resourceName, category);
}
/**
* Removes the given resource from all categories.
*
* @param cms the cms context
* @param resourcePath the resource to reset the categories for
*
* @throws CmsException if something goes wrong
*/
public void clearCategoriesForResource(CmsObject cms, String resourcePath) throws CmsException {
CmsRelationFilter filter = CmsRelationFilter.TARGETS;
filter = filter.filterType(CmsRelationType.CATEGORY);
cms.deleteRelationsFromResource(resourcePath, filter);
}
/**
* Creates a new category.
*
* Will use the same category repository as the parent if specified,
* or the closest category repository to the reference path if specified,
* or the centralized category repository in all other cases.
*
* @param cms the current cms context
* @param parent the parent category or null
for a new top level category
* @param name the name of the new category
* @param title the title
* @param description the description
* @param referencePath the reference path for the category repository
*
* @return the new created category
*
* @throws CmsException if something goes wrong
*/
public CmsCategory createCategory(
CmsObject cms,
CmsCategory parent,
String name,
String title,
String description,
String referencePath) throws CmsException {
List properties = new ArrayList();
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(title)) {
properties.add(new CmsProperty(CmsPropertyDefinition.PROPERTY_TITLE, title, null));
}
if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(description)) {
properties.add(new CmsProperty(CmsPropertyDefinition.PROPERTY_DESCRIPTION, description, null));
}
String folderPath = "";
if (parent != null) {
folderPath += parent.getRootPath();
} else {
if (referencePath == null) {
folderPath += CmsCategoryService.CENTRALIZED_REPOSITORY;
} else {
List repositories = getCategoryRepositories(cms, referencePath);
// take the last one
folderPath = repositories.get(repositories.size() - 1);
}
}
folderPath = cms.getRequestContext().removeSiteRoot(internalCategoryRootPath(folderPath, name));
CmsResource resource;
try {
resource = cms.createResource(folderPath, CmsResourceTypeFolder.RESOURCE_TYPE_ID, null, properties);
} catch (CmsVfsResourceNotFoundException e) {
// may be is the centralized repository missing, try to create it
cms.createResource(CmsCategoryService.CENTRALIZED_REPOSITORY, CmsResourceTypeFolder.RESOURCE_TYPE_ID);
// now try again
resource = cms.createResource(folderPath, CmsResourceTypeFolder.RESOURCE_TYPE_ID, null, properties);
}
return getCategory(cms, resource);
}
/**
* Deletes the category identified by the given path.
*
* Only the most global category matching the given category path for the
* given resource will be affected.
*
* This method will try to lock the involved resource.
*
* @param cms the current cms context
* @param categoryPath the path of the category to delete
* @param referencePath the reference path to find the category repositories
*
* @throws CmsException if something goes wrong
*/
public void deleteCategory(CmsObject cms, String categoryPath, String referencePath) throws CmsException {
CmsCategory category = readCategory(cms, categoryPath, referencePath);
String folderPath = cms.getRequestContext().removeSiteRoot(category.getRootPath());
CmsLock lock = cms.getLock(folderPath);
if (lock.isNullLock()) {
cms.lockResource(folderPath);
} else if (lock.isLockableBy(cms.getRequestContext().getCurrentUser())) {
cms.changeLock(folderPath);
}
cms.deleteResource(folderPath, CmsResource.DELETE_PRESERVE_SIBLINGS);
}
/**
* Creates a category from the given resource.
*
* @param cms the cms context
* @param resource the resource
*
* @return a category object
*
* @throws CmsException if something goes wrong
*/
public CmsCategory getCategory(CmsObject cms, CmsResource resource) throws CmsException {
CmsProperty title = cms.readPropertyObject(resource, CmsPropertyDefinition.PROPERTY_TITLE, false);
CmsProperty description = cms.readPropertyObject(resource, CmsPropertyDefinition.PROPERTY_DESCRIPTION, false);
return new CmsCategory(
resource.getStructureId(),
resource.getRootPath(),
title.getValue(resource.getName()),
description.getValue(""),
getRepositoryBaseFolderName(cms));
}
/**
* Creates a category from the given category root path.
*
* @param cms the cms context
* @param categoryRootPath the category root path
*
* @return a category object
*
* @throws CmsException if something goes wrong
*/
public CmsCategory getCategory(CmsObject cms, String categoryRootPath) throws CmsException {
CmsResource resource = cms.readResource(cms.getRequestContext().removeSiteRoot(categoryRootPath));
return getCategory(cms, resource);
}
/**
* Returns all category repositories for the given reference path.
*
* @param cms the cms context
* @param referencePath the reference path
*
* @return a list of root paths
*/
public List getCategoryRepositories(CmsObject cms, String referencePath) {
List ret = new ArrayList();
if (referencePath == null) {
ret.add(CmsCategoryService.CENTRALIZED_REPOSITORY);
return ret;
}
String path = referencePath;
if (!CmsResource.isFolder(path)) {
path = CmsResource.getParentFolder(path);
}
if (CmsStringUtil.isEmptyOrWhitespaceOnly(path)) {
path = "/";
}
String categoryBase = getRepositoryBaseFolderName(cms);
do {
String repositoryPath = internalCategoryRootPath(path, categoryBase);
if (cms.existsResource(repositoryPath)) {
ret.add(repositoryPath);
}
path = CmsResource.getParentFolder(path);
} while (path != null);
ret.add(CmsCategoryService.CENTRALIZED_REPOSITORY);
// the order is important in case of conflicts
Collections.reverse(ret);
return ret;
}
/**
* Returns the category repositories base folder name.
*
* @param cms the cms context
*
* @return the category repositories base folder name
*/
public String getRepositoryBaseFolderName(CmsObject cms) {
String value = "";
try {
value = cms.readPropertyObject(
CmsCategoryService.CENTRALIZED_REPOSITORY,
CmsPropertyDefinition.PROPERTY_DEFAULT_FILE,
false).getValue();
} catch (CmsException e) {
if (LOG.isErrorEnabled()) {
LOG.error(e.getLocalizedMessage(), e);
}
}
if (CmsStringUtil.isEmptyOrWhitespaceOnly(value)) {
value = OpenCms.getWorkplaceManager().getCategoryFolder();
}
if (CmsStringUtil.isEmptyOrWhitespaceOnly(value)) {
value = REPOSITORY_BASE_FOLDER;
}
if (!value.endsWith("/")) {
value += "/";
}
if (!value.startsWith("/")) {
value = "/" + value;
}
return value;
}
/**
* Renames/Moves a category from the old path to the new one.
*
* This method will keep all categories in their original repository.
*
* @param cms the current cms context
* @param oldCatPath the path of the category to move
* @param newCatPath the new category path
* @param referencePath the reference path to find the category
*
* @throws CmsException if something goes wrong
*/
public void moveCategory(CmsObject cms, String oldCatPath, String newCatPath, String referencePath)
throws CmsException {
CmsCategory category = readCategory(cms, oldCatPath, referencePath);
String catPath = cms.getRequestContext().removeSiteRoot(category.getRootPath());
CmsLock lock = cms.getLock(catPath);
if (lock.isNullLock()) {
cms.lockResource(catPath);
} else if (lock.isLockableBy(cms.getRequestContext().getCurrentUser())) {
cms.changeLock(catPath);
}
cms.moveResource(
catPath,
cms.getRequestContext().removeSiteRoot(internalCategoryRootPath(category.getBasePath(), newCatPath)));
}
/**
* Returns all categories given some search parameters.
*
* @param cms the current cms context
* @param parentCategoryPath the path of the parent category to get the categories for
* @param includeSubCats if to include all categories, or first level child categories only
* @param referencePath the reference path to find all the category repositories
*
* @return a list of {@link CmsCategory} objects
*
* @throws CmsException if something goes wrong
*/
public List readCategories(
CmsObject cms,
String parentCategoryPath,
boolean includeSubCats,
String referencePath) throws CmsException {
List repositories = getCategoryRepositories(cms, referencePath);
return readCategoriesForRepositories(cms, parentCategoryPath, includeSubCats, repositories);
}
/**
* Returns all categories given some search parameters.
*
* @param cms the current cms context
* @param parentCategoryPath the path of the parent category to get the categories for
* @param includeSubCats if to include all categories, or first level child categories only
* @param repositories a list of root paths
* @return a list of {@link CmsCategory} objects
* @throws CmsException if something goes wrong
*/
public List readCategoriesForRepositories(
CmsObject cms,
String parentCategoryPath,
boolean includeSubCats,
List repositories) throws CmsException {
String catPath = parentCategoryPath;
if (catPath == null) {
catPath = "";
}
Set cats = new HashSet();
// traverse in reverse order, to ensure the set will contain most global categories
Iterator it = repositories.iterator();
while (it.hasNext()) {
String repository = it.next();
try {
cats.addAll(
internalReadSubCategories(cms, internalCategoryRootPath(repository, catPath), includeSubCats));
} catch (CmsVfsResourceNotFoundException e) {
// it may be that the given category is not defined in this repository
// just ignore
}
}
List ret = new ArrayList(cats);
Collections.sort(ret);
return ret;
}
/**
* Reads all categories identified by the given category path for the given reference path.
*
* @param cms the current cms context
* @param categoryPath the path of the category to read
* @param referencePath the reference path to find all the category repositories
*
* @return a list of matching categories, could also be empty, if no category exists with the given path
*
* @throws CmsException if something goes wrong
*/
public CmsCategory readCategory(CmsObject cms, String categoryPath, String referencePath) throws CmsException {
// iterate all possible category repositories, starting with the most global one
Iterator it = getCategoryRepositories(cms, referencePath).iterator();
while (it.hasNext()) {
String repository = it.next();
try {
return getCategory(cms, internalCategoryRootPath(repository, categoryPath));
} catch (CmsVfsResourceNotFoundException e) {
// throw the exception if no repository left
if (!it.hasNext()) {
throw e;
}
}
}
// this will never be executed
return null;
}
/**
* Reads the resources for a category identified by the given category path.
*
* @param cms the current cms context
* @param categoryPath the path of the category to read the resources for
* @param recursive true
if including sub-categories
* @param referencePath the reference path to find all the category repositories
*
* @return a list of {@link CmsResource} objects
*
* @throws CmsException if something goes wrong
*/
public List readCategoryResources(
CmsObject cms,
String categoryPath,
boolean recursive,
String referencePath) throws CmsException {
return readCategoryResources(cms, categoryPath, recursive, referencePath, CmsResourceFilter.DEFAULT);
}
/**
* Reads the resources for a category identified by the given category path.
*
* @param cms the current cms context
* @param categoryPath the path of the category to read the resources for
* @param recursive true
if including sub-categories
* @param referencePath the reference path to find all the category repositories
* @param resFilter the resource filter to use
*
* @return a list of {@link CmsResource} objects
*
* @throws CmsException if something goes wrong
*/
public List readCategoryResources(
CmsObject cms,
String categoryPath,
boolean recursive,
String referencePath,
CmsResourceFilter resFilter) throws CmsException {
Set resources = new HashSet();
CmsRelationFilter filter = CmsRelationFilter.SOURCES.filterType(CmsRelationType.CATEGORY);
if (recursive) {
filter = filter.filterIncludeChildren();
}
CmsCategory category = readCategory(cms, categoryPath, referencePath);
Iterator itRelations = cms.getRelationsForResource(
cms.getRequestContext().removeSiteRoot(category.getRootPath()),
filter).iterator();
while (itRelations.hasNext()) {
CmsRelation relation = itRelations.next();
try {
resources.add(relation.getSource(cms, resFilter));
} catch (CmsException e) {
// source does not match the filter
if (LOG.isDebugEnabled()) {
LOG.debug(e.getLocalizedMessage(), e);
}
}
}
List result = new ArrayList(resources);
Collections.sort(result);
return result;
}
/**
* Reads the categories for a resource.
*
* @param cms the current cms context
* @param resource the resource to get the categories for
*
* @return the categories list
*
* @throws CmsException if something goes wrong
*/
public List readResourceCategories(CmsObject cms, CmsResource resource) throws CmsException {
return internalReadResourceCategories(cms, resource, false);
}
/**
* Reads the categories for a resource identified by the given resource name.
*
* @param cms the current cms context
* @param resourceName the path of the resource to get the categories for
*
* @return the categories list
*
* @throws CmsException if something goes wrong
*/
public List readResourceCategories(CmsObject cms, String resourceName) throws CmsException {
return internalReadResourceCategories(cms, cms.readResource(resourceName), false);
}
/**
* Removes a resource identified by the given resource name from the given category.
*
* The resource has to be previously locked.
*
* @param cms the current cms context
* @param resourceName the site relative path to the resource to remove
* @param category the category to remove the resource from
*
* @throws CmsException if something goes wrong
*/
public void removeResourceFromCategory(CmsObject cms, String resourceName, CmsCategory category)
throws CmsException {
// remove the resource just from this category
CmsRelationFilter filter = CmsRelationFilter.TARGETS;
filter = filter.filterType(CmsRelationType.CATEGORY);
filter = filter.filterResource(
cms.readResource(cms.getRequestContext().removeSiteRoot(category.getRootPath())));
filter = filter.filterIncludeChildren();
cms.deleteRelationsFromResource(resourceName, filter);
}
/**
* Removes a resource identified by the given resource name from the category
* identified by the given category path.
*
* The resource has to be previously locked.
*
* @param cms the current cms context
* @param resourceName the site relative path to the resource to remove
* @param categoryPath the path of the category to remove the resource from
*
* @throws CmsException if something goes wrong
*/
public void removeResourceFromCategory(CmsObject cms, String resourceName, String categoryPath)
throws CmsException {
CmsCategory category = readCategory(cms, categoryPath, resourceName);
removeResourceFromCategory(cms, resourceName, category);
}
/**
* Repairs broken category relations.
*
* This could be caused by renaming/moving a category folder,
* or changing the category repositories base folder name.
*
* Also repairs problems when creating/deleting conflicting
* category folders across several repositories.
*
* The resource has to be previously locked.
*
* @param cms the cms context
* @param resource the resource to repair
*
* @throws CmsException if something goes wrong
*/
public void repairRelations(CmsObject cms, CmsResource resource) throws CmsException {
internalReadResourceCategories(cms, resource, true);
}
/**
* Repairs broken category relations.
*
* This could be caused by renaming/moving a category folder,
* or changing the category repositories base folder name.
*
* Also repairs problems when creating/deleting conflicting
* category folders across several repositories.
*
* The resource has to be previously locked.
*
* @param cms the cms context
* @param resourceName the site relative path to the resource to repair
*
* @throws CmsException if something goes wrong
*/
public void repairRelations(CmsObject cms, String resourceName) throws CmsException {
repairRelations(cms, cms.readResource(resourceName));
}
/**
* Composes the category root path by appending the category path to the given category repository path.
*
* @param basePath the category repository path
* @param categoryPath the category path
*
* @return the category root path
*/
private String internalCategoryRootPath(String basePath, String categoryPath) {
if (categoryPath.startsWith("/") && basePath.endsWith("/")) {
// one slash too much
return basePath + categoryPath.substring(1);
} else if (!categoryPath.startsWith("/") && !basePath.endsWith("/")) {
// one slash too less
return basePath + "/" + categoryPath;
} else {
return basePath + categoryPath;
}
}
/**
* Reads/Repairs the categories for a resource identified by the given resource name.
*
* For reparation, the resource has to be previously locked.
*
* @param cms the current cms context
* @param resource the resource to get the categories for
* @param repair if to repair broken relations
*
* @return the categories list
*
* @throws CmsException if something goes wrong
*/
private List internalReadResourceCategories(CmsObject cms, CmsResource resource, boolean repair)
throws CmsException {
List result = new ArrayList();
String baseFolder = null;
Iterator itRelations = cms.getRelationsForResource(
resource,
CmsRelationFilter.TARGETS.filterType(CmsRelationType.CATEGORY)).iterator();
if (repair && itRelations.hasNext()) {
baseFolder = getRepositoryBaseFolderName(cms);
}
String resourceName = cms.getSitePath(resource);
boolean repaired = false;
while (itRelations.hasNext()) {
CmsRelation relation = itRelations.next();
try {
CmsResource res = relation.getTarget(cms, CmsResourceFilter.DEFAULT_FOLDERS);
CmsCategory category = getCategory(cms, res);
if (!repair) {
result.add(category);
} else {
CmsCategory actualCat = readCategory(cms, category.getPath(), resourceName);
if (!category.getId().equals(actualCat.getId())) {
// repair broken categories caused by creation/deletion of
// category folders across several repositories
CmsRelationFilter filter = CmsRelationFilter.TARGETS.filterType(
CmsRelationType.CATEGORY).filterResource(res);
cms.deleteRelationsFromResource(resourceName, filter);
repaired = true;
// set the right category
String catPath = cms.getRequestContext().removeSiteRoot(actualCat.getRootPath());
cms.addRelationToResource(resourceName, catPath, CmsRelationType.CATEGORY.getName());
}
result.add(actualCat);
}
} catch (CmsException e) {
if (!repair) {
if (LOG.isErrorEnabled()) {
LOG.error(e.getLocalizedMessage(), e);
}
} else {
// repair broken categories caused by moving category folders
// could also happen when deleting an assigned category folder
if (LOG.isDebugEnabled()) {
LOG.debug(e.getLocalizedMessage(), e);
}
CmsRelationFilter filter = CmsRelationFilter.TARGETS.filterType(
CmsRelationType.CATEGORY).filterPath(relation.getTargetPath());
if (!relation.getTargetId().isNullUUID()) {
filter = filter.filterStructureId(relation.getTargetId());
}
cms.deleteRelationsFromResource(resourceName, filter);
repaired = true;
// try to set the right category again
try {
CmsCategory actualCat = readCategory(
cms,
CmsCategory.getCategoryPath(relation.getTargetPath(), baseFolder),
resourceName);
addResourceToCategory(cms, resourceName, actualCat);
result.add(actualCat);
} catch (CmsException ex) {
if (LOG.isDebugEnabled()) {
LOG.debug(e.getLocalizedMessage(), ex);
}
}
}
}
}
if (!repair) {
Collections.sort(result);
} else if (repaired) {
// be sure that no higher level category is missing
Iterator it = result.iterator();
while (it.hasNext()) {
CmsCategory category = it.next();
addResourceToCategory(cms, resourceName, category.getPath());
}
}
return result;
}
/**
* Returns all sub categories of the given one, including sub sub categories if needed.
*
* @param cms the current cms context
* @param rootPath the base category's root path (this category is not part of the result)
* @param includeSubCats flag to indicate if sub categories should also be read
*
* @return a list of {@link CmsCategory} objects
*
* @throws CmsException if something goes wrong
*/
private List internalReadSubCategories(CmsObject cms, String rootPath, boolean includeSubCats)
throws CmsException {
List categories = new ArrayList();
List resources = cms.readResources(
cms.getRequestContext().removeSiteRoot(rootPath),
CmsResourceFilter.DEFAULT.addRequireType(CmsResourceTypeFolder.RESOURCE_TYPE_ID),
includeSubCats);
Iterator it = resources.iterator();
while (it.hasNext()) {
CmsResource resource = it.next();
categories.add(getCategory(cms, resource));
}
return categories;
}
}