001/* 002 * Copyright 2010 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.web.filter; 017 018import java.io.IOException; 019import java.util.ArrayList; 020import java.util.Iterator; 021import java.util.List; 022import java.util.Map; 023 024import org.apache.avalon.framework.component.Component; 025import org.apache.avalon.framework.service.ServiceException; 026import org.apache.avalon.framework.service.ServiceManager; 027import org.apache.avalon.framework.service.Serviceable; 028import org.apache.cocoon.components.source.impl.SitemapSource; 029import org.apache.cocoon.xml.AttributesImpl; 030import org.apache.cocoon.xml.XMLUtils; 031import org.apache.excalibur.source.SourceResolver; 032import org.slf4j.Logger; 033import org.slf4j.LoggerFactory; 034import org.xml.sax.ContentHandler; 035import org.xml.sax.SAXException; 036 037import org.ametys.cms.content.ContentHelper; 038import org.ametys.cms.filter.ContentFilter; 039import org.ametys.cms.repository.Content; 040import org.ametys.core.user.CurrentUserProvider; 041import org.ametys.core.user.UserIdentity; 042import org.ametys.core.util.DateUtils; 043import org.ametys.core.util.IgnoreRootHandler; 044import org.ametys.plugins.repository.AmetysObjectIterable; 045import org.ametys.web.filter.WebContentFilter.AccessLimitation; 046import org.ametys.web.pageaccess.ContentAccessManager; 047import org.ametys.web.pageaccess.ContentAccessManager.ContentAccess; 048import org.ametys.web.renderingcontext.RenderingContextHandler; 049import org.ametys.web.repository.content.WebContent; 050import org.ametys.web.repository.page.Page; 051 052/** 053 * Component helper for manipulating {@link ContentFilter} 054 */ 055public class ContentFilterHelper implements Component, Serviceable 056{ 057 /** The Avalon Role */ 058 public static final String ROLE = ContentFilterHelper.class.getName(); 059 060 /** Logger */ 061 protected static final Logger __LOGGER = LoggerFactory.getLogger(ContentFilterHelper.class); 062 063 /** The content access manager. */ 064 protected ContentAccessManager _contentAccessManager; 065 066 /** The rendering context handler. */ 067 protected RenderingContextHandler _renderingContentHandler; 068 069 /** The current user provider */ 070 protected CurrentUserProvider _currentUserProvider; 071 072 /** The content helper */ 073 protected ContentHelper _contentHelper; 074 075 private SourceResolver _resolver; 076 077 078 @Override 079 public void service(ServiceManager manager) throws ServiceException 080 { 081 _resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 082 _contentAccessManager = (ContentAccessManager) manager.lookup(ContentAccessManager.ROLE); 083 _renderingContentHandler = (RenderingContextHandler) manager.lookup(RenderingContextHandler.ROLE); 084 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 085 _contentHelper = (ContentHelper) manager.lookup(ContentHelper.ROLE); 086 } 087 088 /** 089 * SAX all contents matching the given filter 090 * @param handler The content handler to SAX into 091 * @param filter The filter 092 * @throws SAXException If an error occurs while SAXing 093 * @throws IOException If an error occurs while retrieving content. 094 */ 095 public void saxMatchingContents (ContentHandler handler, ContentFilter filter) throws SAXException, IOException 096 { 097 AmetysObjectIterable<Content> contents = filter.getMatchingContents(); 098 Iterator<Content> it = contents.iterator(); 099 100 int index = 0; 101 while (it.hasNext() && index < filter.getLength()) 102 { 103 Content content = it.next(); 104 saxContent(handler, content, filter.getView(), false); 105 index++; 106 } 107 } 108 109 /** 110 * SAX all contents matching the given filter 111 * @param handler The content handler to SAX into 112 * @param filter The filter 113 * @param siteName The current site name. Can be null. 114 * @param lang The current language. Can be null. 115 * @param page The current page. Can be null. 116 * @throws SAXException If an error occurs while SAXing 117 * @throws IOException If an error occurs while retrieving content. 118 */ 119 public void saxMatchingContents (ContentHandler handler, WebContentFilter filter, String siteName, String lang, Page page) throws SAXException, IOException 120 { 121 long t0 = System.currentTimeMillis(); 122 123 boolean checkUserAccess = filter.getAccessLimitation() == AccessLimitation.USER_ACCESS; 124 125 AmetysObjectIterable<Content> contents = filter.getMatchingContents(siteName, lang, page); 126 Iterator<Content> it = contents.iterator(); 127 128 if (__LOGGER.isDebugEnabled()) 129 { 130 long t1 = System.currentTimeMillis(); 131 __LOGGER.debug("get matching content processing time " + (t1 - t0) + " ms"); 132 } 133 134 int index = 0; 135 while (it.hasNext() && index < filter.getLength()) 136 { 137 long t00 = System.currentTimeMillis(); 138 139 Content content = it.next(); 140 141 if (isContentValid(content, page, filter)) 142 { 143 saxContent(handler, content, filter.getView(), checkUserAccess); 144 index++; 145 } 146 147 long t01 = System.currentTimeMillis(); 148 if (__LOGGER.isDebugEnabled()) 149 { 150 __LOGGER.debug("saxing content " + index + " processing time " + (t01 - t00) + " ms"); 151 } 152 } 153 154 if (__LOGGER.isDebugEnabled()) 155 { 156 long t2 = System.currentTimeMillis(); 157 __LOGGER.debug("saxing all contents processing time " + (t2 - t0) + " ms"); 158 } 159 } 160 161 /** 162 * Get the ID of all contents matching the given filter. 163 * This method filters orphan contents and limits the results if necessary. 164 * @param filter The filter 165 * @param siteName The current site name. Can be null. 166 * @param lang The current language. Can be null. 167 * @param page The current page. Can be null. 168 * @return a List of all content IDs. 169 */ 170 public List<String> getMatchingContentIds(WebContentFilter filter, String siteName, String lang, Page page) 171 { 172 List<String> contentIds = new ArrayList<>(); 173 174 long t0 = System.currentTimeMillis(); 175 176 if (__LOGGER.isDebugEnabled()) 177 { 178 long t1 = System.currentTimeMillis(); 179 __LOGGER.debug("get matching content processing time " + (t1 - t0) + " ms"); 180 } 181 182 try (AmetysObjectIterable<Content> contents = filter.getMatchingContents(siteName, lang, page);) 183 { 184 Iterator<Content> it = contents.iterator(); 185 186 int index = 0; 187 while (it.hasNext() && index < filter.getLength()) 188 { 189 long t00 = System.currentTimeMillis(); 190 191 Content content = it.next(); 192 if (isContentValid(content, page, filter)) 193 { 194 contentIds.add(content.getId()); 195 index++; 196 } 197 198 if (__LOGGER.isDebugEnabled()) 199 { 200 long t01 = System.currentTimeMillis(); 201 __LOGGER.debug("get content " + index + " processing time " + (t01 - t00) + " ms"); 202 } 203 } 204 205 return contentIds; 206 } 207 finally 208 { 209 if (__LOGGER.isDebugEnabled()) 210 { 211 long t2 = System.currentTimeMillis(); 212 __LOGGER.debug("get all contents processing time " + (t2 - t0) + " ms"); 213 } 214 } 215 } 216 217 /** 218 * SAX a content in its specific view 219 * @param handler The content handler to SAX into 220 * @param content The content to SAX 221 * @param viewName The view to use 222 * @param checkUserAccess Set to true to check FO user access when rendering content 223 * @throws SAXException If an error occurs while SAXing 224 * @throws IOException If an error occurs while retrieving content. 225 */ 226 public void saxContent (ContentHandler handler, Content content, String viewName, boolean checkUserAccess) throws SAXException, IOException 227 { 228 AttributesImpl attrs = new AttributesImpl(); 229 attrs.addCDATAAttribute("id", content.getId()); 230 attrs.addCDATAAttribute("name", content.getName()); 231 attrs.addCDATAAttribute("title", content.getTitle(null)); 232 attrs.addCDATAAttribute("lastModifiedAt", DateUtils.zonedDateTimeToString(content.getLastModified())); 233 234 XMLUtils.startElement(handler, "content", attrs); 235 236 String uri = _contentHelper.getContentHtmlViewUrl(content, viewName, Map.of("checkUserAccess", String.valueOf(checkUserAccess))); 237 SitemapSource src = null; 238 239 try 240 { 241 src = (SitemapSource) _resolver.resolveURI(uri); 242 src.toSAX(new IgnoreRootHandler(handler)); 243 } 244 finally 245 { 246 _resolver.release(src); 247 } 248 249 XMLUtils.endElement(handler, "content"); 250 } 251 252 /** 253 * Test if the content is accessible in accordance with the filter options (mask orphans, access limitation policy, ...) 254 * @param content the content to test. 255 * @param currentPage the current page. 256 * @param filter the content filter. 257 * @return true if the content is accessible, false otherwise. 258 */ 259 public boolean isContentValid(Content content, Page currentPage, ContentFilter filter) 260 { 261 if (filter instanceof WebContentFilter) 262 { 263 WebContentFilter webFilter = (WebContentFilter) filter; 264 265 boolean maskOrphan = webFilter.maskOrphanContents(); 266 267 if (!maskOrphan 268 || maskOrphan && !isOrphan(content)) 269 { 270 return isAccessible(content, currentPage, webFilter); 271 } 272 273 return false; 274 } 275 276 return true; 277 } 278 279 /** 280 * Test if the content is accessible in accordance with the filter's access limitation policy. 281 * @param content the content to test. 282 * @param currentPage the current page. 283 * @param filter the web content filter. 284 * @return true if the content is accessible, false otherwise. 285 */ 286 protected boolean isAccessible(Content content, Page currentPage, WebContentFilter filter) 287 { 288 if (content instanceof WebContent) 289 { 290 WebContent webContent = (WebContent) content; 291 292 AccessLimitation accessLimitation = filter.getAccessLimitation(); 293 UserIdentity user = _currentUserProvider.getUser(); 294 295 if (accessLimitation == AccessLimitation.PAGE_ACCESS) 296 { 297 if (currentPage == null) 298 { 299 // The current page is null when coming from a static filter 300 // Test if the content is displayed in at least one public page (anonymous access) 301 return _contentAccessManager.getAccess(webContent, null, false) == ContentAccess.UNRESTRICTED; 302 } 303 else 304 { 305 // Test if the content is displayed in a page having the same access rights as the current page. 306 return _contentAccessManager.isAccessibleByPage(webContent, currentPage); 307 } 308 } 309 else if (accessLimitation == AccessLimitation.USER_ACCESS) 310 { 311 // Test if the content is displayed in a page accessible by the current user. 312 ContentAccess access = _contentAccessManager.getAccess(webContent, user); 313 return access == ContentAccess.ALLOWED || access == ContentAccess.UNRESTRICTED; 314 } 315 } 316 317 return true; 318 } 319 320 /** 321 * Determines if a content is orphan 322 * @param content the content to test 323 * @return true if the content is orphan 324 */ 325 protected boolean isOrphan (Content content) 326 { 327 if (content instanceof WebContent) 328 { 329 return ((WebContent) content).getReferencingPages().isEmpty(); 330 } 331 return true; 332 } 333 334}