Skip to content

Commit

Permalink
#28896 Including listParents as attribute on the response of the chil…
Browse files Browse the repository at this point in the history
…dren Categories Endpoint
  • Loading branch information
freddyDOTCMS committed Jul 3, 2024
1 parent a30aa11 commit bb8cd6c
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,6 @@ Category findByVariable(final String variable, final User user,
*
* @return List of Category filtered
*/
PaginatedCategories findAll(final CategorySearchCriteria searchCriteria,
final User user, boolean respectFrontendRoles)
PaginatedCategories findAll(final CategorySearchCriteria searchCriteria, final User user, boolean respectFrontendRoles)
throws DotDataException, DotSecurityException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -970,7 +970,9 @@ public PaginatedCategories findAll(final CategorySearchCriteria searchCriteria,
throw new IllegalArgumentException("Limit must be greater than 0");
}

final List<Category> categories = permissionAPI.filterCollection(categoryFactory.findAll(searchCriteria),
final List<Category> allCategories = new ArrayList<>(categoryFactory.findAll(searchCriteria));

final List<Category> categories = permissionAPI.filterCollection(allCategories,
PermissionAPI.PERMISSION_READ, respectFrontendRoles, user);

return getCategoriesSubList(searchCriteria.offset, searchCriteria.limit, categories, null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.dotcms.util.pagination.OrderDirection;
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.portlets.categories.model.Category;
import com.dotmarketing.portlets.categories.model.HierarchedCategory;
import com.liferay.portal.model.User;

import javax.ws.rs.DefaultValue;
Expand Down Expand Up @@ -287,7 +288,7 @@ public abstract class CategoryFactory {
*
* @param searchCriteria Search Criteria
*
* @return List of Category filtered
* @return List of Category filteredx
*/
public abstract List<Category> findAll(final CategorySearchCriteria searchCriteria) throws DotDataException;
public abstract List<HierarchedCategory> findAll(final CategorySearchCriteria searchCriteria) throws DotDataException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.dotcms.util.CloseUtils;
import com.dotcms.util.DotPreconditions;
import com.dotcms.util.JsonUtil;
import com.dotcms.util.ReflectionUtils;
import com.dotmarketing.beans.Tree;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.business.CacheLocator;
Expand All @@ -17,12 +19,15 @@
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.factories.TreeFactory;
import com.dotmarketing.portlets.categories.model.Category;
import com.dotmarketing.portlets.categories.model.HierarchedCategory;
import com.dotmarketing.portlets.categories.model.ShortCategory;
import com.dotmarketing.util.InodeUtils;
import com.dotmarketing.util.Logger;
import com.dotmarketing.util.UtilMethods;
import com.dotmarketing.util.VelocityUtil;
import com.liferay.util.StringPool;

import java.io.IOException;
import java.io.Serializable;
import java.sql.*;
import java.util.ArrayList;
Expand All @@ -40,6 +45,7 @@
*/
public class CategoryFactoryImpl extends CategoryFactory {

public static final String INODE = "inode";
CategoryCache catCache;
final CategorySQL categorySQL;

Expand Down Expand Up @@ -602,21 +608,56 @@ protected List<Category> findTopLevelCategoriesByFilter(String filter, String so
* @param sqlResults sql query results
* @return a list of categories objects
*/
List<Category> convertForCategories(final List<Map<String, Object>> sqlResults) {

List<Category> convertForCategories(final List<Map<String, Object>> sqlResults) {
List<Category> categories = new ArrayList<>();

if ( sqlResults != null ) {

for ( Map<String, Object> row : sqlResults ) {
Category category = convertForCategory(row);
Category category = convertForCategory(row, Category.class);
categories.add(category);
}
}

return categories;
}

private List<HierarchedCategory> convertForHierarchedCategories(final List<Map<String, Object>> sqlResults) {
List<HierarchedCategory> categories = new ArrayList<>();

if ( sqlResults != null ) {

for ( Map<String, Object> row : sqlResults ) {
HierarchedCategory category = (HierarchedCategory) convertForCategory(row, HierarchedCategory.class);

try {
final String parentsASJsonArray = "[" + row.get("path") + "]";

final List<ShortCategory> parentList = ((List<Map<String, String>>) JsonUtil.getObjectFromJson(parentsASJsonArray, List.class))
.stream()
.map(map -> new ShortCategory.Builder()
.setCategoryName(map.get("categoryName"))
.setKey(map.get("key"))
.setInode(map.get(INODE))
.build()
)
.collect(Collectors.toList());

category.setParentList(parentList.subList(0, parentList.size() - 1));
categories.add(category);
} catch (IOException e) {
Logger.warn(CategoryFactoryImpl.class, e::getMessage);
}
}
}

return categories;
}

private Category convertForCategory(final Map<String, Object> sqlResult) {
return convertForCategory(sqlResult, Category.class);
}

/**
* Converts the category information coming from the database into a {@link Category}
* object with all of its properties. If the information is not present, a
Expand All @@ -625,11 +666,11 @@ List<Category> convertForCategories(final List<Map<String, Object>> sqlResults)
* @param sqlResult - The data of a specific category from the database.
* @return The {@link Category} object.
*/
private Category convertForCategory(final Map<String, Object> sqlResult) {
private Category convertForCategory(final Map<String, Object> sqlResult, Class<? extends Category> clazz) {

Category category = null;
if ( sqlResult != null ) {
category = new Category();
category = ReflectionUtils.newInstance(clazz);

Object sortOrder = sqlResult.get("sort_order");

Expand Down Expand Up @@ -841,11 +882,11 @@ protected String suggestVelocityVarName(final String categoryVelVarName) throws
* @return
* @throws DotDataException
*/
public List<Category> findAll(final CategorySearchCriteria searchCriteria)
public List<HierarchedCategory> findAll(final CategorySearchCriteria searchCriteria)
throws DotDataException {

if (!UtilMethods.isSet(searchCriteria.rootInode) && !UtilMethods.isSet(searchCriteria.filter)) {
return findAll();
if (searchCriteria.rootInode == null) {
throw new IllegalArgumentException();
}

final String query = getFindAllSQLQuery(searchCriteria);
Expand All @@ -862,22 +903,26 @@ public List<Category> findAll(final CategorySearchCriteria searchCriteria)
dc.addObject("%" + searchCriteria.filter.toLowerCase() + "%");
}

final List<Category> categories = convertForCategories(UtilMethods.isSet(searchCriteria.rootInode) ?
final List<Map<String, Object>> results = UtilMethods.isSet(searchCriteria.rootInode) ?
dc.loadObjectResults().stream()
.filter(map -> !map.get("inode").equals(searchCriteria.rootInode))
.collect(Collectors.toList()) : dc.loadObjectResults());
.collect(Collectors.toList()) : dc.loadObjectResults();

final List<HierarchedCategory> categories = convertForHierarchedCategories(results);

updateCache(categories);
return categories;
}

private static String getFindAllSQLQuery(CategorySearchCriteria searchCriteria) {
final String queryTemplate = "WITH RECURSIVE CategoryHierarchy AS ( " +
"SELECT c.* FROM Category c %s " +
"SELECT c.*, json_build_object('inode', inode, 'categoryName', category_name, 'key', category_key)::varchar AS path " +
"FROM Category c %s " +
"UNION ALL " +
"SELECT c.* FROM Category c JOIN tree t ON c.inode = t.child JOIN CategoryHierarchy ch ON t.parent = ch.inode " +
"SELECT c.*, CONCAT(ch.path, ',', json_build_object('inode', c.inode, 'categoryName', c.category_name, 'key', c.category_key)::varchar) AS path " +
"FROM Category c JOIN tree t ON c.inode = t.child JOIN CategoryHierarchy ch ON t.parent = ch.inode " +
") " +
"SELECT * FROM CategoryHierarchy %s ORDER BY %s %s";
"SELECT distinct *,path FROM CategoryHierarchy %s ORDER BY %s %s";

final String rootCategoryFilter = UtilMethods.isSet(searchCriteria.rootInode) ? "WHERE c.inode = ?" : StringPool.BLANK;

Expand All @@ -886,12 +931,11 @@ private static String getFindAllSQLQuery(CategorySearchCriteria searchCriteria)
"LOWER(category_key) LIKE ? OR " +
"LOWER(category_velocity_var_name) LIKE ?" : StringPool.BLANK;

final String query = String.format(queryTemplate, rootCategoryFilter, filterCategories, searchCriteria.orderBy,
return String.format(queryTemplate, rootCategoryFilter, filterCategories, searchCriteria.orderBy,
searchCriteria.direction.toString());
return query;
}

private void updateCache(List<Category> categories) throws DotDataException {
private void updateCache(List<? extends Category> categories) throws DotDataException {
for(final Category category : categories) {
if(catCache.get(category.getInode()) == null)
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.liferay.portal.model.User;
import org.apache.commons.lang.builder.ToStringBuilder;

import javax.ws.rs.NotSupportedException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
Expand Down Expand Up @@ -267,5 +268,4 @@ public ManifestInfo getManifestInfo() {
.title(this.getCategoryName())
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.dotmarketing.portlets.categories.model;

import java.util.List;

/**
* Represents a {@link Category} with its hierarchy calculated.
* This means traversing from the current category all the way up to the first-level category that you encounter.
*
* For example:
*
* | Name | Parent | hierarchy |
* |-------------|----------------|------------------------ |
* | Top Category| null | [] |
* | Child | Top Category | [Top Category] |
* | Grand Child | Child | [Top Category, Child] |
*
*/
public class HierarchedCategory extends Category{

private List<ShortCategory> parentList;

public void setParentList(final List<ShortCategory> parentList) {
this.parentList = parentList;
}

public List<ShortCategory> getParentList() {
return parentList;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.dotmarketing.portlets.categories.model;

/**
* Represents a {@link Category}, but only contains the most important data:
*
* - Category's name
* - Category's key
* - Category's inode
*/
public class ShortCategory {

private String categoryName;
private String inode;
private String key;

private ShortCategory(final Builder builder) {
this.categoryName = builder.categoryName;
this.inode = builder.inode;
this.key = builder.key;
}

public String getCategoryName() {
return categoryName;
}

public String getInode() {
return inode;
}

public String getKey() {
return key;
}

public static class Builder {
private String categoryName;
private String inode;
private String key;

public Builder setCategoryName(String categoryName) {
this.categoryName = categoryName;
return this;
}

public Builder setInode(String inode) {
this.inode = inode;
return this;
}

public Builder setKey(String key) {
this.key = key;
return this;
}

public ShortCategory build() {
return new ShortCategory(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import com.dotmarketing.exception.DotDataException;
import com.dotmarketing.exception.DotSecurityException;
import com.dotmarketing.portlets.categories.model.Category;
import com.dotmarketing.portlets.categories.model.HierarchedCategory;
import com.dotmarketing.portlets.contentlet.business.ContentletAPI;
import com.dotmarketing.portlets.contentlet.business.HostAPI;
import com.dotmarketing.portlets.contentlet.model.Contentlet;
Expand Down Expand Up @@ -1545,7 +1546,7 @@ public void test_save_createTopLevelCategory_asLimitedUser_fail()
}

/**
* Method to test: {@link CategoryAPIImpl#findAll(CategoryFactory.CategorySearchCriteria, User, boolean)}
* Method to test: {@link CategoryAPIImpl#findAll(CategorySearchCriteria, User, boolean)}
* When: Call the API method
* Should: it should
* - Use the {@link CategoryFactoryImpl#findAll(CategorySearchCriteria)} to search the {@link Category}
Expand All @@ -1567,12 +1568,12 @@ public void getAllCategoriesFiltered() throws DotDataException, DotSecurityExcep

final User user = mock();

final Category category1 = mock(Category.class);
final Category category2 = mock(Category.class);
final Category category3 = mock(Category.class);
final HierarchedCategory category1 = mock(HierarchedCategory.class);
final HierarchedCategory category2 = mock(HierarchedCategory.class);
final HierarchedCategory category3 = mock(HierarchedCategory.class);

final List<Category> categoriesAfterSearch = list(category1, category2, category3);
final List<Category> categoriesAfterPermission = list(category1, category2);
final List<HierarchedCategory> categoriesAfterSearch = list(category1, category2, category3);
final List<HierarchedCategory> categoriesAfterPermission = list(category1, category2);

final CategoryFactory categoryFactory = mock();
when(categoryFactory.findAll(searchingCriteria)).thenReturn(categoriesAfterSearch);
Expand Down Expand Up @@ -1621,19 +1622,19 @@ public void getAllCategoriesFilteredWithPagination() throws DotDataException, Do
.offset(5)
.build();

final Category category1 = mock(Category.class);
final Category category2 = mock(Category.class);
final Category category3 = mock(Category.class);
final Category category4 = mock(Category.class);
final Category category5 = mock(Category.class);
final Category category6 = mock(Category.class);
final Category category7 = mock(Category.class);
final Category category8 = mock(Category.class);
final Category category9 = mock(Category.class);
final HierarchedCategory category1 = mock(HierarchedCategory.class);
final HierarchedCategory category2 = mock(HierarchedCategory.class);
final HierarchedCategory category3 = mock(HierarchedCategory.class);
final HierarchedCategory category4 = mock(HierarchedCategory.class);
final HierarchedCategory category5 = mock(HierarchedCategory.class);
final HierarchedCategory category6 = mock(HierarchedCategory.class);
final HierarchedCategory category7 = mock(HierarchedCategory.class);
final HierarchedCategory category8 = mock(HierarchedCategory.class);
final HierarchedCategory category9 = mock(HierarchedCategory.class);

final User user = mock();

final List<Category> categories = list(category1, category2, category3, category4, category5, category6,
final List<HierarchedCategory> categories = list(category1, category2, category3, category4, category5, category6,
category7, category8, category9);

final CategoryFactory categoryFactory = mock();
Expand Down
Loading

0 comments on commit bb8cd6c

Please sign in to comment.