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