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, 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.ade.detailpage; 029 030import org.opencms.file.CmsObject; 031import org.opencms.file.CmsResource; 032import org.opencms.gwt.shared.CmsGwtConstants; 033import org.opencms.main.CmsException; 034import org.opencms.main.CmsLog; 035import org.opencms.main.OpenCms; 036import org.opencms.relations.CmsCategory; 037import org.opencms.relations.CmsCategoryService; 038import org.opencms.util.CmsPath; 039 040import java.util.ArrayList; 041import java.util.Collections; 042import java.util.Comparator; 043import java.util.HashSet; 044import java.util.List; 045import java.util.Set; 046import java.util.stream.Stream; 047 048import org.apache.commons.logging.Log; 049 050/** 051 * Filters and sorts a list of detail pages based on whether they are suitable detail pages for a fixed detail content. 052 * 053 * <p>Note: Filtering on detail page types is already handled elsewhere, so this class only handles detail pages for a single type. 054 */ 055public class CmsDetailPageFilter { 056 057 /** Prefix for the category qualifier in a detail page entry. */ 058 public static final String PREFIX_CATEGORY = "category:"; 059 060 /** Logger instance for this class. */ 061 private static final Log LOG = CmsLog.getLog(CmsDetailPageFilter.class); 062 063 /** The CMS context. */ 064 private CmsObject m_cms; 065 066 /** The detail resource (either this or m_path is null). */ 067 private CmsResource m_resource; 068 069 /** The categories of the detail resource (lazily initialized). */ 070 private Set<CmsPath> m_categories; 071 072 /** The detail root path (either this or m_resource is null). */ 073 private String m_path; 074 075 /** 076 * Creates a new instance based on an actual detail resource. 077 * 078 * @param cms the CMS context 079 * @param resource the detail resource 080 */ 081 public CmsDetailPageFilter(CmsObject cms, CmsResource resource) { 082 083 try { 084 m_cms = OpenCms.initCmsObject(cms); 085 m_cms.getRequestContext().setSiteRoot(""); 086 m_resource = resource; 087 } catch (CmsException e) { 088 // shouldn't happen - initCmsObject doesn't *actually* throw exceptions 089 LOG.error(e.getLocalizedMessage(), e); 090 } 091 092 } 093 094 /** 095 * Creates a new instance based on the root path of a detail resource. 096 * 097 * @param cms the CMS context 098 * @param rootPath the detail resource root path 099 */ 100 public CmsDetailPageFilter(CmsObject cms, String rootPath) { 101 102 try { 103 m_cms = OpenCms.initCmsObject(cms); 104 m_cms.getRequestContext().setSiteRoot(""); 105 m_path = rootPath; 106 } catch (CmsException e) { 107 // shouldn't happen 108 LOG.error(e.getLocalizedMessage(), e); 109 } 110 111 } 112 113 /** 114 * 115 * Produces a filtered and sorted result stream of candidate detail pages for the detail resource. 116 * 117 * @param infos the base list of detail page beans to filter/sort 118 * @return the result stream 119 */ 120 public Stream<CmsDetailPageInfo> filterDetailPages(List<CmsDetailPageInfo> infos) { 121 122 List<CmsDetailPageInfo> infos2 = new ArrayList<>(infos); 123 124 // the sort method uses a stable sort, so detail page order will be preserved for those entries with the same sort key 125 Collections.sort(infos2, new Comparator<CmsDetailPageInfo>() { 126 127 public int compare(CmsDetailPageInfo a, CmsDetailPageInfo b) { 128 129 return Integer.compare(getSortKey(a), getSortKey(b)); 130 } 131 132 private int getSortKey(CmsDetailPageInfo info) { 133 134 // sort order: 135 // 0 - qualified non-default detail pages 136 // 1 - unqualified non-default detail pages 137 // 2 - qualified default detail pages 138 // 3 - unqualified default detail pages 139 boolean defaultDetailFlag = info.getType().equals(CmsGwtConstants.DEFAULT_DETAILPAGE_TYPE); 140 boolean qualifierFlag = info.getQualifier() != null; 141 if (!defaultDetailFlag) { 142 if (qualifierFlag) { 143 return 0; 144 } else { 145 return 1; 146 } 147 } else { 148 if (qualifierFlag) { 149 return 2; 150 } else { 151 return 3; 152 } 153 } 154 } 155 }); 156 // qualified pages shouldn't work without an unqualified fallback 157 if (!infos2.stream().anyMatch(detailPage -> detailPage.getQualifier() == null)) { 158 if ((infos2.size() != 0) && LOG.isWarnEnabled()) { 159 LOG.warn( 160 "No unqualified detail page entries found in list - probably a configuration error: " + infos2); 161 } 162 return Collections.<CmsDetailPageInfo> emptyList().stream(); 163 } 164 return infos2.stream().filter(info -> (info.getQualifier() == null) || checkQualifier(info.getQualifier())); 165 } 166 167 /** 168 * Checks that a detail page qualifier matches the detail resource. 169 * 170 * @param qualifier the qualifier to check 171 * @return true if the qualifier matches the detail resource 172 */ 173 protected boolean checkQualifier(String qualifier) { 174 175 // shouldn't happen, test anyway 176 if (qualifier == null) { 177 return true; 178 } 179 qualifier = qualifier.trim(); 180 if (qualifier.startsWith(PREFIX_CATEGORY)) { 181 String categoryStr = qualifier.substring(PREFIX_CATEGORY.length()); 182 // use CmsPath to normalize leading/trailing slashes 183 CmsPath categoryPath = new CmsPath(categoryStr); 184 return getCategories().contains(categoryPath); 185 } 186 LOG.error("Invalid detail page qualifier: " + qualifier); 187 return false; 188 } 189 190 /** 191 * Gets the category paths, lazily initializing them first if necessary. 192 * 193 * @return the set of category paths 194 */ 195 protected Set<CmsPath> getCategories() { 196 197 if (m_categories == null) { 198 m_categories = readCategories(); 199 } 200 return m_categories; 201 } 202 203 /** 204 * Reads the categories for the resource. 205 * 206 * @return the categories for the resource 207 */ 208 protected Set<CmsPath> readCategories() { 209 210 try { 211 List<CmsCategory> categories; 212 213 if (m_resource != null) { 214 categories = CmsCategoryService.getInstance().readResourceCategories(m_cms, m_resource); 215 } else { 216 categories = CmsCategoryService.getInstance().readResourceCategories(m_cms, m_path); 217 } 218 Set<CmsPath> result = new HashSet<>(); 219 for (CmsCategory category : categories) { 220 result.add(new CmsPath(category.getPath())); 221 } 222 return result; 223 } catch (Exception e) { 224 LOG.error(e.getLocalizedMessage(), e); 225 // empty result is cached, so we do not retry 226 return Collections.emptySet(); 227 } 228 229 } 230 231}