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.PagesContainer;
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(PagesContainer 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                for (ZoneItem item : zone.getZoneItems())
175                {
176                    switch (item.getType())
177                    {
178                        case CONTENT:
179                            values.put("contents", values.get("contents") + 1);
180                            break;
181                        case SERVICE:
182                            values.put("services", values.get("services") + 1);
183                            
184                            // Test if the service is cacheable, only if the page is cacheable since then.
185                            if (isCacheable)
186                            {
187                                Service service = _serviceExtPt.getExtension(item.getServiceId());
188                                if (service != null && !service.isCacheable(page, item))
189                                {
190                                    isCacheable = false;
191                                }
192                            }
193                            break;
194                        default:
195                            break;
196                    }
197                }
198            }
199            
200            if (isCacheable)
201            {
202                cacheableCount++;
203            }
204            
205            _processPages(page, values);
206        }
207        
208        values.put("nbCacheable", values.get("nbCacheable") + cacheableCount);
209    }
210    
211    private void _saxContents(Site site) throws SAXException, RepositoryException
212    {
213        AmetysObjectIterable<Content> contents = site.getContents();
214        
215        AttributesImpl atts = new AttributesImpl();
216        atts.addCDATAAttribute("count", String.valueOf(contents.getSize()));
217
218        int orphaned = 0;
219        int external = 0;
220        for (Content content : contents)
221        {
222            int referers = 0;
223            int ext = 0;
224            
225            // FIXME API getNode
226            PropertyIterator it = ((JCRAmetysObject) content).getNode().getReferences();
227            while (it.hasNext())
228            {
229                Property property = it.nextProperty();
230                Node node = property.getParent();
231                if (!node.isNodeType("oswf:entry"))
232                {
233                    referers++;
234                    
235                    if (node.isNodeType("ametys:zoneItem"))
236                    {
237                        Node parent = node.getParent();
238                        while (!parent.isNodeType("ametys:site"))
239                        {
240                            parent = parent.getParent();
241                        }
242                        
243                        if (!parent.getName().equals(site.getName()))
244                        {
245                            ext++;
246                        }
247                    }
248                }
249            }
250            
251            if (referers == 0)
252            {
253                orphaned++;
254            }
255            
256            if (ext > 0)
257            {
258                external++;
259            }
260        }
261        
262        atts.addCDATAAttribute("orphaned", String.valueOf(orphaned));
263        atts.addCDATAAttribute("external", String.valueOf(external));
264
265        XMLUtils.createElement(contentHandler, "contents", atts);
266    }
267    
268    private void _saxResources(Site site) throws SAXException
269    {
270        HashMap<String, Long> values = new HashMap<>();
271        values.put("folderCount", 0L);
272        values.put("resourceCount", 0L);
273        values.put("totalSize", 0L);
274        
275        _processResources(site.getRootResources(), values);
276        
277        long folderCount = values.get("folderCount");
278        long resourceCount = values.get("resourceCount");
279        long totalSize = values.get("totalSize");
280        
281        AttributesImpl atts = new AttributesImpl();
282        atts.addCDATAAttribute("folderCount", Long.toString(folderCount));
283        atts.addCDATAAttribute("resourceCount", Long.toString(resourceCount));
284        atts.addCDATAAttribute("totalSize", Long.toString(totalSize));
285
286        XMLUtils.createElement(contentHandler, "resources", atts);
287    }
288
289    private void _processResources(TraversableAmetysObject resourceContainer, HashMap<String, Long> values)
290    {
291        AmetysObjectIterable<? extends AmetysObject> objects = resourceContainer.getChildren();
292        
293        for (AmetysObject object : objects)
294        {
295            // Traverse the child nodes if depth < 0 (generate all) or depth > 0 (we're not in the last level).
296            if (object instanceof ResourceCollection)
297            {
298                values.put("folderCount", values.get("folderCount") + 1);
299                
300                _processResources((ResourceCollection) object, values);
301            }
302            else if (object instanceof Resource)
303            {
304                Resource resource = (Resource) object;
305                
306                values.put("resourceCount", values.get("resourceCount") + 1);
307                values.put("totalSize", values.get("totalSize") + resource.getLength());
308            }
309        }
310    }
311    
312}