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 261 || maskOrphan && !isOrphan(content)) 262 { 263 return isAccessible(content, currentPage, webFilter); 264 } 265 266 return false; 267 } 268 269 return true; 270 } 271 272 /** 273 * Test if the content is accessible in accordance with the filter's access limitation policy. 274 * @param content the content to test. 275 * @param currentPage the current page. 276 * @param filter the web content filter. 277 * @return true if the content is accessible, false otherwise. 278 */ 279 protected boolean isAccessible(Content content, Page currentPage, WebContentFilter filter) 280 { 281 if (content instanceof WebContent) 282 { 283 WebContent webContent = (WebContent) content; 284 285 AccessLimitation accessLimitation = filter.getAccessLimitation(); 286 UserIdentity user = _currentUserProvider.getUser(); 287 288 if (accessLimitation == AccessLimitation.PAGE_ACCESS) 289 { 290 if (currentPage == null) 291 { 292 // The current page is null when coming from a static filter 293 // Test if the content is displayed in at least one public page (anonymous access) 294 return _contentAccessManager.getAccess(webContent, null, false) == ContentAccess.UNRESTRICTED; 295 } 296 else 297 { 298 // Test if the content is displayed in a page having the same access rights as the current page. 299 return _contentAccessManager.isAccessibleByPage(webContent, currentPage); 300 } 301 } 302 else if (accessLimitation == AccessLimitation.USER_ACCESS) 303 { 304 // Test if the content is displayed in a page accessible by the current user. 305 ContentAccess access = _contentAccessManager.getAccess(webContent, user); 306 return access == ContentAccess.ALLOWED || access == ContentAccess.UNRESTRICTED; 307 } 308 } 309 310 return true; 311 } 312 313 /** 314 * Determines if a content is orphan 315 * @param content the content to test 316 * @return true if the content is orphan 317 */ 318 protected boolean isOrphan (Content content) 319 { 320 if (content instanceof WebContent) 321 { 322 return ((WebContent) content).getReferencingPages().isEmpty(); 323 } 324 return true; 325 } 326 327}