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.site;
017
018import java.io.IOException;
019import java.util.HashMap;
020import java.util.Map;
021
022import javax.jcr.Node;
023import javax.jcr.Property;
024import javax.jcr.PropertyIterator;
025import javax.jcr.RepositoryException;
026
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.cocoon.ProcessingException;
030import org.apache.cocoon.environment.ObjectModelHelper;
031import org.apache.cocoon.environment.Request;
032import org.apache.cocoon.generation.ServiceableGenerator;
033import org.apache.cocoon.xml.AttributesImpl;
034import org.apache.cocoon.xml.XMLUtils;
035import org.xml.sax.SAXException;
036
037import org.ametys.cms.repository.Content;
038import org.ametys.plugins.explorer.resources.Resource;
039import org.ametys.plugins.explorer.resources.ResourceCollection;
040import org.ametys.plugins.repository.AmetysObject;
041import org.ametys.plugins.repository.AmetysObjectIterable;
042import org.ametys.plugins.repository.TraversableAmetysObject;
043import org.ametys.plugins.repository.jcr.JCRAmetysObject;
044import org.ametys.web.cache.PageHelper;
045import org.ametys.web.repository.page.Page;
046import org.ametys.web.repository.page.SitemapElement;
047import org.ametys.web.repository.page.Zone;
048import org.ametys.web.repository.page.ZoneItem;
049import org.ametys.web.repository.site.Site;
050import org.ametys.web.repository.site.SiteManager;
051import org.ametys.web.repository.sitemap.Sitemap;
052import org.ametys.web.service.Service;
053import org.ametys.web.service.ServiceExtensionPoint;
054
055/**
056 * Provides some statistics about a {@link Site}, such as :<br>
057 * <ul>
058 * <li>Number of pages
059 * <li>Number of live contents
060 * <li>Number of orphaned contents
061 * <li>Number of external contents
062 * </ul>
063 */
064public class SiteStatisticsGenerator extends ServiceableGenerator
065{
066    /** The site manager. */
067    protected SiteManager _siteManager;
068    
069    /** The service extension point. */
070    protected ServiceExtensionPoint _serviceExtPt;
071    
072    /** The page cache helper. */
073    protected PageHelper _pageCacheHelper;
074    
075    @Override
076    public void service(ServiceManager sManager) throws ServiceException
077    {
078        super.service(sManager);
079        _siteManager = (SiteManager) sManager.lookup(SiteManager.ROLE);
080        _serviceExtPt = (ServiceExtensionPoint) sManager.lookup(ServiceExtensionPoint.ROLE);
081        _pageCacheHelper = (PageHelper) sManager.lookup(PageHelper.ROLE);
082    }
083    
084    @Override
085    public void generate() throws IOException, SAXException, ProcessingException
086    {
087        Request request = ObjectModelHelper.getRequest(objectModel);
088        String siteName = parameters.getParameter("siteName", request.getParameter("siteName"));
089        Site site = _siteManager.getSite(siteName);
090        
091        contentHandler.startDocument();
092        
093        saxSite(site);
094        
095        contentHandler.endDocument();
096    }
097
098    /**
099     * SAX statistics on a site.
100     * @param site the site to SAX statistics on.
101     * @throws SAXException if an error occurs while saxing
102     * @throws ProcessingException if an error occurs
103     */
104    protected void saxSite(Site site) throws SAXException, ProcessingException
105    {
106        AttributesImpl atts = new AttributesImpl();
107        atts.addCDATAAttribute("name", site.getName());
108        atts.addCDATAAttribute("title", site.getTitle());
109
110        XMLUtils.startElement(contentHandler, "site", atts);
111        
112        for (Sitemap sitemap : site.getSitemaps())
113        {
114            _saxSitemap(sitemap);
115        }
116        
117        try
118        {
119            _saxContents(site);
120        }
121        catch (RepositoryException e)
122        {
123            throw new ProcessingException("Unable to process contents for site " + site.getName(), e);
124        }
125        
126        _saxResources(site);
127        
128        XMLUtils.endElement(contentHandler, "site");
129    }
130    
131    /**
132     * SAX statistics on a sitemap.
133     * @param sitemap the sitemap to SAX statistics on.
134     * @throws SAXException if an error occurs while saxing
135     */
136    protected void _saxSitemap(Sitemap sitemap) throws SAXException
137    {
138        HashMap<String, Long> values = new HashMap<>();
139        values.put("nbPages", 0L);
140        values.put("nbCacheable", 0L);
141        values.put("services", 0L);
142        values.put("contents", 0L);
143        
144        _processPages(sitemap, values);
145        
146        long nbPages = values.get("nbPages");
147        long nbCacheable = values.get("nbCacheable");
148        long services = values.get("services");
149        long contents = values.get("contents");
150        
151        AttributesImpl atts = new AttributesImpl();
152        atts.addCDATAAttribute("name", sitemap.getName());
153        atts.addCDATAAttribute("nbPages", String.valueOf(nbPages));
154        atts.addCDATAAttribute("nbCacheable", Long.toString(nbCacheable));
155        atts.addCDATAAttribute("services", String.valueOf(services));
156        atts.addCDATAAttribute("contents", String.valueOf(contents));
157
158        XMLUtils.createElement(contentHandler, "sitemap", atts);
159    }
160    
161    private void _processPages(SitemapElement pages, Map<String, Long> values)
162    {
163        AmetysObjectIterable<? extends Page> childPages = pages.getChildrenPages();
164        
165        values.put("nbPages", values.get("nbPages") + childPages.getSize());
166        
167        int cacheableCount = 0;
168        
169        for (Page page : childPages)
170        {
171            boolean isCacheable = _pageCacheHelper.isCacheable(page);
172            for (Zone zone : page.getZones())
173            {
174                AmetysObjectIterable<? extends ZoneItem> zoneItems = zone.getZoneItems();
175                for (ZoneItem item : zoneItems)
176                {
177                    switch (item.getType())
178                    {
179                        case CONTENT:
180                            values.put("contents", values.get("contents") + 1);
181                            break;
182                        case SERVICE:
183                            values.put("services", values.get("services") + 1);
184                            
185                            // Test if the service is cacheable, only if the page is cacheable since then.
186                            if (isCacheable)
187                            {
188                                Service service = _serviceExtPt.getExtension(item.getServiceId());
189                                if (service != null && !service.isCacheable(page, item))
190                                {
191                                    isCacheable = false;
192                                }
193                            }
194                            break;
195                        default:
196                            break;
197                    }
198                }
199            }
200            
201            if (isCacheable)
202            {
203                cacheableCount++;
204            }
205            
206            _processPages(page, values);
207        }
208        
209        values.put("nbCacheable", values.get("nbCacheable") + cacheableCount);
210    }
211    
212    private void _saxContents(Site site) throws SAXException, RepositoryException
213    {
214        AmetysObjectIterable<Content> contents = site.getContents();
215        
216        AttributesImpl atts = new AttributesImpl();
217        atts.addCDATAAttribute("count", String.valueOf(contents.getSize()));
218
219        int orphaned = 0;
220        int external = 0;
221        for (Content content : contents)
222        {
223            int referers = 0;
224            int ext = 0;
225            
226            // FIXME API getNode
227            PropertyIterator it = ((JCRAmetysObject) content).getNode().getReferences();
228            while (it.hasNext())
229            {
230                Property property = it.nextProperty();
231                Node node = property.getParent();
232                if (!node.isNodeType("oswf:entry"))
233                {
234                    referers++;
235                    
236                    if (node.isNodeType("ametys:zoneItem"))
237                    {
238                        Node parent = node.getParent();
239                        while (!parent.isNodeType("ametys:site"))
240                        {
241                            parent = parent.getParent();
242                        }
243                        
244                        if (!parent.getName().equals(site.getName()))
245                        {
246                            ext++;
247                        }
248                    }
249                }
250            }
251            
252            if (referers == 0)
253            {
254                orphaned++;
255            }
256            
257            if (ext > 0)
258            {
259                external++;
260            }
261        }
262        
263        atts.addCDATAAttribute("orphaned", String.valueOf(orphaned));
264        atts.addCDATAAttribute("external", String.valueOf(external));
265
266        XMLUtils.createElement(contentHandler, "contents", atts);
267    }
268    
269    private void _saxResources(Site site) throws SAXException
270    {
271        HashMap<String, Long> values = new HashMap<>();
272        values.put("folderCount", 0L);
273        values.put("resourceCount", 0L);
274        values.put("totalSize", 0L);
275        
276        _processResources(site.getRootResources(), values);
277        
278        long folderCount = values.get("folderCount");
279        long resourceCount = values.get("resourceCount");
280        long totalSize = values.get("totalSize");
281        
282        AttributesImpl atts = new AttributesImpl();
283        atts.addCDATAAttribute("folderCount", Long.toString(folderCount));
284        atts.addCDATAAttribute("resourceCount", Long.toString(resourceCount));
285        atts.addCDATAAttribute("totalSize", Long.toString(totalSize));
286
287        XMLUtils.createElement(contentHandler, "resources", atts);
288    }
289
290    private void _processResources(TraversableAmetysObject resourceContainer, HashMap<String, Long> values)
291    {
292        AmetysObjectIterable<? extends AmetysObject> objects = resourceContainer.getChildren();
293        
294        for (AmetysObject object : objects)
295        {
296            // Traverse the child nodes if depth < 0 (generate all) or depth > 0 (we're not in the last level).
297            if (object instanceof ResourceCollection)
298            {
299                values.put("folderCount", values.get("folderCount") + 1);
300                
301                _processResources((ResourceCollection) object, values);
302            }
303            else if (object instanceof Resource)
304            {
305                Resource resource = (Resource) object;
306                
307                values.put("resourceCount", values.get("resourceCount") + 1);
308                values.put("totalSize", values.get("totalSize") + resource.getLength());
309            }
310        }
311    }
312    
313}