001/* 002 * Copyright 2015 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.site; 017 018import java.util.ArrayList; 019import java.util.HashMap; 020import java.util.List; 021import java.util.Map; 022 023import javax.jcr.Node; 024import javax.jcr.Property; 025import javax.jcr.PropertyIterator; 026import javax.jcr.RepositoryException; 027 028import org.apache.avalon.framework.parameters.Parameters; 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031import org.apache.cocoon.ProcessingException; 032import org.apache.cocoon.acting.ServiceableAction; 033import org.apache.cocoon.environment.ObjectModelHelper; 034import org.apache.cocoon.environment.Redirector; 035import org.apache.cocoon.environment.Request; 036import org.apache.cocoon.environment.SourceResolver; 037 038import org.ametys.cms.repository.Content; 039import org.ametys.core.cocoon.JSonReader; 040import org.ametys.plugins.explorer.resources.Resource; 041import org.ametys.plugins.explorer.resources.ResourceCollection; 042import org.ametys.plugins.repository.AmetysObject; 043import org.ametys.plugins.repository.AmetysObjectIterable; 044import org.ametys.plugins.repository.TraversableAmetysObject; 045import org.ametys.plugins.repository.jcr.JCRAmetysObject; 046import org.ametys.runtime.i18n.I18nizableText; 047import org.ametys.web.cache.PageHelper; 048import org.ametys.web.repository.page.Page; 049import org.ametys.web.repository.page.PagesContainer; 050import org.ametys.web.repository.page.Zone; 051import org.ametys.web.repository.page.ZoneItem; 052import org.ametys.web.repository.site.Site; 053import org.ametys.web.repository.site.SiteManager; 054import org.ametys.web.repository.sitemap.Sitemap; 055import org.ametys.web.service.Service; 056import org.ametys.web.service.ServiceExtensionPoint; 057 058/** 059 * Provides some statistics about a {@link Site}, such as :<br> 060 * <ul> 061 * <li>Number of pages 062 * <li>Number of live contents 063 * <li>Number of orphaned contents 064 * <li>Number of external contents 065 * </ul> 066 */ 067public class SiteStatisticsAction extends ServiceableAction 068{ 069 070 /** The site manager. */ 071 protected SiteManager _siteManager; 072 073 /** The service extension point. */ 074 protected ServiceExtensionPoint _serviceExtPt; 075 076 /** The page cache helper. */ 077 protected PageHelper _pageCacheHelper; 078 079 @Override 080 public void service(ServiceManager sManager) throws ServiceException 081 { 082 super.service(sManager); 083 _siteManager = (SiteManager) sManager.lookup(SiteManager.ROLE); 084 _serviceExtPt = (ServiceExtensionPoint) sManager.lookup(ServiceExtensionPoint.ROLE); 085 _pageCacheHelper = (PageHelper) sManager.lookup(PageHelper.ROLE); 086 } 087 088 public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception 089 { 090 Map<String, Object> result = new HashMap<>(); 091 092 Request request = ObjectModelHelper.getRequest(objectModel); 093 String siteName = parameters.getParameter("siteName", request.getParameter("siteName")); 094 Site site = _siteManager.getSite(siteName); 095 096 List<Map<String, Object>> statistics = new ArrayList<>(); 097 098 // Statistics on sitemaps 099 for (Sitemap sitemap : site.getSitemaps()) 100 { 101 statistics.add(_sitemap2json(sitemap)); 102 } 103 104 105 try 106 { 107 // Statistics on contents 108 statistics.add(_contents2json(site)); 109 } 110 catch (RepositoryException e) 111 { 112 throw new ProcessingException("Unable to process contents for site " + site.getName(), e); 113 } 114 115 // Statistics on resources 116 statistics.add(_resources2json(site)); 117 118 result.put("values", statistics); 119 120 request.setAttribute(JSonReader.OBJECT_TO_READ, result); 121 122 return EMPTY_MAP; 123 } 124 125 /** 126 * Statistics on contents 127 * @param site the site 128 * @return the statistics on contents 129 * @throws RepositoryException if an error occurs 130 */ 131 protected Map<String, Object> _contents2json (Site site) throws RepositoryException 132 { 133 Map<String, Object> contents2json = new HashMap<>(); 134 135 contents2json.put("label", new I18nizableText("plugin.web", "PLUGINS_WEB_ADMINISTRATOR_SITE_STATISTICS_CONTENTS")); 136 contents2json.put("type", "contents"); 137 138 HashMap<String, Long> values = new HashMap<>(); 139 values.put("COUNT", 0L); 140 values.put("ORPHANED", 0L); 141 values.put("EXTERNAL", 0L); 142 143 _processContents (site, values); 144 145 List<Map<String, Object>> values2json = new ArrayList<>(); 146 147 for (String name : values.keySet()) 148 { 149 Map<String, Object> stat = new HashMap<>(); 150 stat.put("label", new I18nizableText("plugin.web", "PLUGINS_WEB_ADMINISTRATOR_SITE_STATISTICS_VALUE_CONTENTS_" + name)); 151 stat.put("value", values.get(name)); 152 stat.put("values", new ArrayList<>()); 153 values2json.add(stat); 154 } 155 156 contents2json.put("values", values2json); 157 158 return contents2json; 159 } 160 161 /** 162 * Process the contents of the site to gather information on them 163 * @param site the site 164 * @param values the information on the contents 165 * @throws RepositoryException if an error occurs on the repository 166 */ 167 protected void _processContents (Site site, Map<String, Long> values) throws RepositoryException 168 { 169 // Number of contents 170 AmetysObjectIterable<Content> contents = site.getContents(); 171 values.put("COUNT", values.get("COUNT") + contents.getSize()); 172 173 int orphaned = 0; 174 int external = 0; 175 for (Content content : contents) 176 { 177 int referers = 0; 178 int ext = 0; 179 180 // FIXME API getNode 181 PropertyIterator it = ((JCRAmetysObject) content).getNode().getReferences(); 182 while (it.hasNext()) 183 { 184 Property property = it.nextProperty(); 185 Node node = property.getParent(); 186 if (!node.isNodeType("oswf:entry")) 187 { 188 referers++; 189 190 if (node.isNodeType("ametys:zoneItem")) 191 { 192 Node parent = node.getParent(); 193 while (!parent.isNodeType("ametys:site")) 194 { 195 parent = parent.getParent(); 196 } 197 198 if (!parent.getName().equals(site.getName())) 199 { 200 ext++; 201 } 202 } 203 } 204 } 205 206 if (referers == 0) 207 { 208 orphaned++; 209 } 210 211 if (ext > 0) 212 { 213 external++; 214 } 215 } 216 217 // Number of orphan contents 218 values.put("ORPHANED", values.get("ORPHANED") + orphaned); 219 220 // Number of shared contents 221 values.put("EXTERNAL", values.get("ORPHANED") + external); 222 } 223 224 /** 225 * Statistics on resources 226 * @param site the site 227 * @return the statistics on resources 228 */ 229 protected Map<String, Object> _resources2json(Site site) 230 { 231 Map<String, Object> resources2json = new HashMap<>(); 232 233 resources2json.put("label", new I18nizableText("plugin.web", "PLUGINS_WEB_ADMINISTRATOR_SITE_STATISTICS_RESOURCES")); 234 resources2json.put("type", "resources"); 235 236 HashMap<String, Long> values = new HashMap<>(); 237 values.put("FOLDERCOUNT", 0L); 238 values.put("RESOURCECOUNT", 0L); 239 values.put("TOTALSIZE", 0L); 240 241 _processResources(site.getRootResources(), values); 242 243 List<Map<String, Object>> values2json = new ArrayList<>(); 244 245 for (String name : values.keySet()) 246 { 247 Map<String, Object> stat = new HashMap<>(); 248 stat.put("label", new I18nizableText("plugin.web", "PLUGINS_WEB_ADMINISTRATOR_SITE_STATISTICS_VALUE_RESOURCES_" + name)); 249 stat.put("value", values.get(name)); 250 stat.put("values", new ArrayList<>()); 251 values2json.add(stat); 252 253 } 254 255 resources2json.put("values", values2json); 256 257 return resources2json; 258 } 259 260 /** 261 * Statistics on sitemap 262 * @param sitemap the sitemap 263 * @return the statistics on sitemap 264 */ 265 protected Map<String, Object> _sitemap2json(Sitemap sitemap) 266 { 267 Map<String, Object> sitemap2json = new HashMap<>(); 268 269 sitemap2json.put("name", sitemap.getName()); 270 sitemap2json.put("type", "sitemap"); 271 sitemap2json.put("label", new I18nizableText("plugin.web", "PLUGINS_WEB_ADMINISTRATOR_SITE_STATISTICS_SITEMAP_" + sitemap.getName().toUpperCase())); 272 273 Map<String, Long> values = new HashMap<>(); 274 values.put("NBPAGES", 0L); // Number of pages 275 values.put("NBCACHEABLE", 0L); // Number of cacheable pages 276 values.put("SERVICES", 0L); // Number of services 277 values.put("CONTENTS", 0L); // Number of contents 278 279 _processPages(sitemap, values); 280 281 List<Map<String, Object>> values2json = new ArrayList<>(); 282 283 for (String name : values.keySet()) 284 { 285 Map<String, Object> stat = new HashMap<>(); 286 stat.put("label", new I18nizableText("plugin.web", "PLUGINS_WEB_ADMINISTRATOR_SITE_STATISTICS_VALUE_SITEMAP_" + name)); 287 stat.put("value", values.get(name)); 288 stat.put("values", new ArrayList<>()); 289 values2json.add(stat); 290 291 } 292 293 sitemap2json.put("values", values2json); 294 295 return sitemap2json; 296 } 297 298 /** 299 * Process statistics on a pages container 300 * @param pages The pages container (page or sitemap) 301 * @param values The values 302 */ 303 protected void _processPages(PagesContainer pages, Map<String, Long> values) 304 { 305 AmetysObjectIterable<? extends Page> childPages = pages.getChildrenPages(); 306 307 values.put("NBPAGES", values.get("NBPAGES") + childPages.getSize()); 308 309 int cacheableCount = 0; 310 311 for (Page page : childPages) 312 { 313 boolean isCacheable = _pageCacheHelper.isCacheable(page); 314 for (Zone zone : page.getZones()) 315 { 316 for (ZoneItem item : zone.getZoneItems()) 317 { 318 switch (item.getType()) 319 { 320 case CONTENT: 321 values.put("CONTENTS", values.get("CONTENTS") + 1); 322 break; 323 case SERVICE: 324 values.put("SERVICES", values.get("SERVICES") + 1); 325 326 // Test if the service is cacheable, only if the page is cacheable since then. 327 if (isCacheable) 328 { 329 Service service = _serviceExtPt.getExtension(item.getServiceId()); 330 if (service != null && !service.isCacheable(page, item)) 331 { 332 isCacheable = false; 333 } 334 } 335 break; 336 default: 337 break; 338 } 339 } 340 } 341 342 if (isCacheable) 343 { 344 cacheableCount++; 345 } 346 347 _processPages(page, values); 348 } 349 350 values.put("NBCACHEABLE", values.get("NBCACHEABLE") + cacheableCount); 351 } 352 353 /** 354 * Process statistics on a resources 355 * @param resourceContainer The resources container 356 * @param values The values 357 */ 358 protected void _processResources(TraversableAmetysObject resourceContainer, HashMap<String, Long> values) 359 { 360 AmetysObjectIterable<? extends AmetysObject> objects = resourceContainer.getChildren(); 361 362 for (AmetysObject object : objects) 363 { 364 // Traverse the child nodes if depth < 0 (generate all) or depth > 0 (we're not in the last level). 365 if (object instanceof ResourceCollection) 366 { 367 values.put("FOLDERCOUNT", values.get("FOLDERCOUNT") + 1); 368 369 _processResources((ResourceCollection) object, values); 370 } 371 else if (object instanceof Resource) 372 { 373 Resource resource = (Resource) object; 374 375 values.put("RESOURCECOUNT", values.get("RESOURCECOUNT") + 1); 376 values.put("TOTALSIZE", values.get("TOTALSIZE") + resource.getLength()); 377 } 378 } 379 } 380 381}