001/*
002 *  Copyright 2012 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.plugins.calendar.events;
017
018import java.io.IOException;
019import java.net.URLDecoder;
020import java.util.Calendar;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.Date;
024import java.util.Iterator;
025import java.util.Map;
026import java.util.Set;
027
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.cocoon.ProcessingException;
031import org.apache.cocoon.environment.ObjectModelHelper;
032import org.apache.cocoon.environment.Request;
033import org.apache.cocoon.xml.AttributesImpl;
034import org.apache.cocoon.xml.XMLUtils;
035import org.apache.commons.lang.StringUtils;
036import org.apache.commons.lang.time.DateUtils;
037import org.joda.time.DateTime;
038import org.joda.time.format.ISODateTimeFormat;
039import org.xml.sax.ContentHandler;
040import org.xml.sax.SAXException;
041
042import org.ametys.cms.filter.ContentFilterExtensionPoint;
043import org.ametys.cms.repository.Content;
044import org.ametys.cms.tag.Tag;
045import org.ametys.plugins.repository.AmetysObjectIterable;
046import org.ametys.plugins.repository.AmetysObjectResolver;
047import org.ametys.plugins.repository.query.expression.Expression;
048import org.ametys.web.filter.WebContentFilter;
049import org.ametys.web.filter.WebContentFilter.AccessLimitation;
050import org.ametys.web.repository.page.Page;
051import org.ametys.web.repository.page.ZoneItem;
052
053/**
054 * Query and generate news according to many parameters.
055 */
056public class EventsGenerator extends AbstractEventGenerator
057{
058    /** The ametys object resolver. */
059    protected AmetysObjectResolver _ametysResolver;
060
061    /** The filter extension point. */
062    protected ContentFilterExtensionPoint _filterExtPt;
063
064    /** The events helper */
065    protected EventsFilterHelper _eventsFilterHelper;
066
067    @Override
068    public void service(ServiceManager serviceManager) throws ServiceException
069    {
070        super.service(serviceManager);
071        _filterExtPt = (ContentFilterExtensionPoint) serviceManager.lookup(ContentFilterExtensionPoint.ROLE);
072        _ametysResolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
073        _eventsFilterHelper = (EventsFilterHelper) serviceManager.lookup(EventsFilterHelper.ROLE);
074    }
075
076    @Override
077    public void generate() throws IOException, SAXException, ProcessingException
078    {
079        Request request = ObjectModelHelper.getRequest(objectModel);
080        @SuppressWarnings("unchecked")
081        Map<String, Object> parentContextAttrs = (Map<String, Object>) objectModel.get(ObjectModelHelper.PARENT_CONTEXT);
082        if (parentContextAttrs == null)
083        {
084            parentContextAttrs = Collections.EMPTY_MAP;
085        }
086
087        DateTime today = new DateTime();
088
089        // Get site and language in sitemap parameters. Can not be null.
090        String siteName = parameters.getParameter("site", (String) request.getAttribute("site"));
091        String lang = parameters.getParameter("lang", (String) request.getAttribute("renderingLanguage"));
092        if (StringUtils.isEmpty(lang))
093        {
094            lang = (String) request.getAttribute("sitemapLanguage");
095        }
096        // Get the parameters.
097        int monthsBefore = parameters.getParameterAsInteger("months-before", 3);
098        int monthsAfter = parameters.getParameterAsInteger("months-after", 3);
099        // Type can be "calendar", "single-day" or "agenda".
100        String type = parameters.getParameter("type", "calendar");
101        String view = parameters.getParameter("view", "");
102        int year = parameters.getParameterAsInteger("year", today.getYear());
103        int month = parameters.getParameterAsInteger("month", today.getMonthOfYear());
104        int day = parameters.getParameterAsInteger("day", today.getDayOfMonth());
105        // Select a single tag or "all".
106        String singleTag = parameters.getParameter("tag", "all");
107        if (StringUtils.isNotEmpty(singleTag) && !"all".equals(singleTag))
108        {
109            singleTag = singleTag.toUpperCase();
110        }
111        
112        
113        Page page = (Page) request.getAttribute(Page.class.getName());
114        // Get the zone item, as a request attribute or from the ID in the
115        // parameters.
116        ZoneItem zoneItem = (ZoneItem) request.getAttribute(ZoneItem.class.getName());
117        String zoneItemId = parameters.getParameter("zoneItemId", "");
118        if (zoneItem == null && StringUtils.isNotEmpty(zoneItemId))
119        {
120            zoneItemId = URLDecoder.decode(zoneItemId, "UTF-8");
121            zoneItem = (ZoneItem) _ametysResolver.resolveById(zoneItemId);
122        }
123        
124        if (page == null && zoneItem != null)
125        {
126            // Wrapped page such as _plugins/calendar/page/YEAR/MONTH/DAY/ZONEITEMID/events_1.3.html => get the page from its zone item
127            // The page is needed to get restriction
128            page = zoneItem.getZone().getPage();
129        }
130        
131        DateTime dateTime = new DateTime(year, month, day, 0, 0, 0, 0);
132        String title = _eventsFilterHelper.getTitle(zoneItem);
133        String rangeType = parameters.getParameter("rangeType", _eventsFilterHelper.getDefaultRangeType(zoneItem));
134        boolean maskOrphan = _eventsFilterHelper.getMaskOrphan(zoneItem);
135        AccessLimitation accessLimitation = _eventsFilterHelper.getAccessLimitation(zoneItem);
136        boolean pdfDownload = _eventsFilterHelper.getPdfDownload(zoneItem);
137        boolean icalDownload = _eventsFilterHelper.getIcalDownload(zoneItem);
138        String link = _eventsFilterHelper.getLink(zoneItem);
139        String linkTitle = _eventsFilterHelper.getLinkTitle(zoneItem);
140        
141        boolean doRetrieveView = !StringUtils.equalsIgnoreCase("false", parameters.getParameter("do-retrieve-view", "true"));
142
143        // Get the content types to match
144        Set<String> contentTypes = _eventsFilterHelper.getContentTypes(zoneItem);
145
146        // Get the tags to match, from the zone item or from the parameters.
147        String[] tagsArray = (String[]) parentContextAttrs.get("tags");
148        Set<String> tags = _eventsFilterHelper.getTags(zoneItem, tagsArray);
149        // Get the categories of the tags to match, from the zone item or from
150        // the parameters.
151        String[] tagCategoriesArray = (String[]) parentContextAttrs.get("tag-categories");
152        Set<Tag> categories = _eventsFilterHelper.getTagCategories(zoneItem, siteName, tagCategoriesArray);
153        // Get all tags belonging to the categories.
154        Set<String> categoryTags = _eventsFilterHelper.getAllTags(categories);
155        // Restrain the list to a single tag if provided.
156        if (categoryTags.contains(singleTag) && StringUtils.isNotEmpty(singleTag) && !"all".equals(singleTag))
157        {
158            categoryTags = Collections.singleton(singleTag);
159        }
160
161        String pagePath = page != null ? page.getPathInSitemap() : "";
162
163        // Get the declared events filter.
164        EventsFilter eventsFilter = (EventsFilter) _filterExtPt.getExtension(EventsFilterHelper.EVENTS_FILTER_ID);
165
166        // Get the date range and deduce the expression (single day or
167        // month-before to month-after).
168        EventsFilterHelper.DateRange dateRange = _eventsFilterHelper.getDateRange(type, year, month, day, monthsBefore, monthsAfter, rangeType);
169        Expression expression = _eventsFilterHelper.getExpression(type, dateRange);
170
171        // Configure the filter to return the wanted contents.
172        _eventsFilterHelper.configureFilter(eventsFilter, expression, contentTypes, tags, categoryTags, view, maskOrphan, accessLimitation);
173
174        // Get the corresponding contents.
175        AmetysObjectIterable<Content> eventContents = eventsFilter.getMatchingContents(siteName, lang, page);
176
177        AttributesImpl atts = new AttributesImpl();
178
179        atts.addCDATAAttribute("page-path", pagePath);
180        atts.addCDATAAttribute("today", ISODateTimeFormat.date().print(today));
181        if (dateRange.getStartDate() != null)
182        {
183            atts.addCDATAAttribute("start", ISODateTimeFormat.date().print(new DateTime(dateRange.getStartDate())));
184        }
185        if (dateRange.getEndDate() != null)
186        {
187            atts.addCDATAAttribute("end", ISODateTimeFormat.date().print(new DateTime(dateRange.getEndDate())));
188        }
189
190        atts.addCDATAAttribute("year", Integer.toString(year));
191        atts.addCDATAAttribute("month", String.format("%02d", month));
192        atts.addCDATAAttribute("day", String.format("%02d", day));
193        atts.addCDATAAttribute("months-before", Integer.toString(monthsBefore));
194        atts.addCDATAAttribute("months-after", Integer.toString(monthsAfter));
195
196        atts.addCDATAAttribute("title", title);
197        atts.addCDATAAttribute("mask-orphan", Boolean.toString(maskOrphan));
198        atts.addCDATAAttribute("pdf-download", Boolean.toString(pdfDownload));
199        atts.addCDATAAttribute("ical-download", Boolean.toString(icalDownload));
200        atts.addCDATAAttribute("link", link);
201        atts.addCDATAAttribute("link-title", linkTitle);
202
203        if (zoneItem != null)
204        {
205            atts.addCDATAAttribute("zoneItemId", zoneItem.getId());
206        }
207        if (StringUtils.isNotEmpty(rangeType))
208        {
209            atts.addCDATAAttribute("range", rangeType);
210        }
211        if (StringUtils.isNotEmpty(singleTag))
212        {
213            atts.addCDATAAttribute("single-tag", singleTag);
214        }
215
216        contentHandler.startDocument();
217        XMLUtils.startElement(contentHandler, "events", atts);
218
219        _saxRssUrl(zoneItem);
220
221        // Generate months (used in calendar mode) and days (used in full-page
222        // agenda mode).
223        _saxMonths(dateRange);
224        _saxDays(dateTime.toDate(), rangeType);
225
226        _saxDaysNew(dateRange, rangeType);
227
228        // Generate tags and categories.
229        _saxTags(tags);
230        _saxCategories(categories);
231
232        // Generate the matching contents.
233        XMLUtils.startElement(contentHandler, "contents");
234
235        saxMatchingContents(contentHandler, eventsFilter, eventContents, page, doRetrieveView);
236
237        XMLUtils.endElement(contentHandler, "contents");
238
239        XMLUtils.endElement(contentHandler, "events");
240        contentHandler.endDocument();
241    }
242
243    private void _saxRssUrl(ZoneItem zoneItem) throws SAXException 
244    {
245        if (zoneItem != null && zoneItem.getServiceParameters().getBoolean("rss", false))
246        {
247            // Split protocol and id
248            String[] zoneItemId = zoneItem.getId().split("://");
249            String url = "_plugins/calendar/" + zoneItemId[1] + "/rss.xml";
250            
251            XMLUtils.createElement(contentHandler, "rssUrl", url);
252        }
253    }
254    
255    /**
256     * SAX all contents matching the given filter
257     * 
258     * @param handler The content handler to SAX into
259     * @param filter The filter
260     * @param contents iterator on the contents.
261     * @param currentPage The current page.
262     * @param saxContentItSelf true to sax the content, false will only sax some meta
263     * @throws SAXException If an error occurs while SAXing
264     * @throws IOException If an error occurs while retrieving content.
265     */
266    public void saxMatchingContents(ContentHandler handler, WebContentFilter filter, AmetysObjectIterable<Content> contents, Page currentPage, boolean saxContentItSelf) throws SAXException, IOException
267    {
268        boolean checkUserAccess = filter.getAccessLimitation() == AccessLimitation.USER_ACCESS;
269        
270        for (Content content : contents)
271        {
272            if (_filterHelper.isContentValid(content, currentPage, filter))
273            {
274                saxContent(handler, content, saxContentItSelf, filter, checkUserAccess);
275            }
276        }
277    }
278
279    /**
280     * SAX information on the months spanning the date range.
281     * @param dateRange the date range.
282     * @throws SAXException if a error occurs while saxing
283     */
284    protected void _saxMonths(EventsFilterHelper.DateRange dateRange) throws SAXException
285    {
286        if (dateRange != null && dateRange.getStartDate() != null && dateRange.getEndDate() != null)
287        {
288            AttributesImpl atts = new AttributesImpl();
289
290            XMLUtils.startElement(contentHandler, "months");
291
292            DateTime date = new DateTime(dateRange.getStartDate());
293            DateTime end = new DateTime(dateRange.getEndDate());
294
295            while (date.isBefore(end))
296            {
297                int year = date.getYear();
298                int month = date.getMonthOfYear();
299
300                String monthStr = String.format("%d-%02d", year, month);
301                String dateStr = ISODateTimeFormat.dateTime().print(date);
302
303                atts.clear();
304                atts.addCDATAAttribute("str", monthStr);
305                atts.addCDATAAttribute("raw", dateStr);
306                XMLUtils.startElement(contentHandler, "month", atts);
307
308                XMLUtils.endElement(contentHandler, "month");
309
310                date = date.plusMonths(1);
311            }
312
313            XMLUtils.endElement(contentHandler, "months");
314        }
315    }
316
317    /**
318     * Generate days to build a "calendar" view.
319     * 
320     * @param dateRange a date belonging to the time span to generate.
321     * @param rangeType the range type, "month" or "week".
322     * @throws SAXException if an error occurs while saxing
323     */
324    protected void _saxDaysNew(EventsFilterHelper.DateRange dateRange, String rangeType) throws SAXException
325    {
326        XMLUtils.startElement(contentHandler, "calendar-months");
327
328        DateTime date = new DateTime(dateRange.getStartDate());
329        DateTime end = new DateTime(dateRange.getEndDate());
330
331        while (date.isBefore(end))
332        {
333            int year = date.getYear();
334            int month = date.getMonthOfYear();
335
336            String monthStr = String.format("%d-%02d", year, month);
337            String dateStr = ISODateTimeFormat.dateTime().print(date);
338
339            AttributesImpl attrs = new AttributesImpl();
340            attrs.addCDATAAttribute("str", monthStr);
341            attrs.addCDATAAttribute("raw", dateStr);
342            attrs.addCDATAAttribute("year", Integer.toString(year));
343            attrs.addCDATAAttribute("month", Integer.toString(month));
344            XMLUtils.startElement(contentHandler, "month", attrs);
345
346            _saxDays(date.toDate(), "month");
347
348            XMLUtils.endElement(contentHandler, "month");
349
350            date = date.plusMonths(1);
351        }
352
353        XMLUtils.endElement(contentHandler, "calendar-months");
354    }
355
356    /**
357     * Generate days to build a "calendar" view.
358     * 
359     * @param date a date belonging to the time span to generate.
360     * @param type the range type, "month" or "week".
361     * @throws SAXException if an error occurs while saxing
362     */
363    protected void _saxDays(Date date, String type) throws SAXException
364    {
365        AttributesImpl attrs = new AttributesImpl();
366
367        DateTime dateTime = new DateTime(date);
368
369        int rangeStyle = DateUtils.RANGE_MONTH_MONDAY;
370        DateTime previousDay = null;
371        DateTime nextDay = null;
372
373        // Week.
374        if ("week".equals(type))
375        {
376            rangeStyle = DateUtils.RANGE_WEEK_MONDAY;
377
378            // Get the first day of the week.
379            previousDay = dateTime.dayOfWeek().withMinimumValue();
380            // First day of next week.
381            nextDay = previousDay.plusWeeks(1);
382            // First day of previous week.
383            previousDay = previousDay.minusWeeks(1);
384        }
385        else
386        {
387            rangeStyle = DateUtils.RANGE_MONTH_MONDAY;
388
389            // Get the first day of the month.
390            previousDay = dateTime.dayOfMonth().withMinimumValue();
391            // First day of previous month.
392            nextDay = previousDay.plusMonths(1);
393            // First day of next month.
394            previousDay = previousDay.minusMonths(1);
395        }
396
397        addNavAttributes(attrs, new DateTime(date), previousDay, nextDay);
398
399        // Get an iterator on the days to be present on the calendar.
400        Iterator<Calendar> days = DateUtils.iterator(date, rangeStyle);
401
402        XMLUtils.startElement(contentHandler, "calendar", attrs);
403
404        DateTime previousWeekDay = dateTime.minusWeeks(1);
405        DateTime nextWeekDay = dateTime.plusWeeks(1);
406
407        AttributesImpl weekAttrs = new AttributesImpl();
408        addNavAttributes(weekAttrs, new DateTime(date), previousWeekDay, nextWeekDay);
409
410        XMLUtils.startElement(contentHandler, "week", weekAttrs);
411
412        while (days.hasNext())
413        {
414            Calendar dayCal = days.next();
415            DateTime day = new DateTime(dayCal);
416            String rawDateStr = ISODateTimeFormat.dateTime().print(day);
417            String dateStr = ISODateTimeFormat.date().print(day);
418            String yearStr = Integer.toString(dayCal.get(Calendar.YEAR));
419            String monthStr = Integer.toString(dayCal.get(Calendar.MONTH) + 1);
420            String dayStr = Integer.toString(dayCal.get(Calendar.DAY_OF_MONTH));
421
422            AttributesImpl dayAttrs = new AttributesImpl();
423
424            dayAttrs.addCDATAAttribute("raw", rawDateStr);
425            dayAttrs.addCDATAAttribute("date", dateStr);
426            dayAttrs.addCDATAAttribute("year", yearStr);
427            dayAttrs.addCDATAAttribute("month", monthStr);
428            dayAttrs.addCDATAAttribute("day", dayStr);
429
430            XMLUtils.createElement(contentHandler, "day", dayAttrs);
431
432            // Break on week on the last day of the week (but not on the last
433            // week).
434            if (dayCal.get(Calendar.DAY_OF_WEEK) == _eventsFilterHelper.getLastDayOfWeek(dayCal) && days.hasNext())
435            {
436                previousWeekDay = day.minusDays(6);
437                nextWeekDay = day.plusDays(8);
438                weekAttrs.clear();
439
440                addNavAttributes(weekAttrs, day, previousWeekDay, nextWeekDay);
441
442                XMLUtils.endElement(contentHandler, "week");
443                XMLUtils.startElement(contentHandler, "week", weekAttrs);
444            }
445        }
446
447        XMLUtils.endElement(contentHandler, "week");
448        XMLUtils.endElement(contentHandler, "calendar");
449    }
450
451    /**
452     * Add nav attributes.
453     * 
454     * @param attrs the attributes object to fill in.
455     * @param current the current date.
456     * @param previousDay the previous date.
457     * @param nextDay the next date.
458     */
459    protected void addNavAttributes(AttributesImpl attrs, DateTime current, DateTime previousDay, DateTime nextDay)
460    {
461        attrs.addCDATAAttribute("current", ISODateTimeFormat.dateTime().print(current));
462
463        attrs.addCDATAAttribute("previous", ISODateTimeFormat.dateTime().print(previousDay));
464        attrs.addCDATAAttribute("previousYear", previousDay.year().getAsString());
465        attrs.addCDATAAttribute("previousMonth", previousDay.monthOfYear().getAsString());
466        attrs.addCDATAAttribute("previousDay", previousDay.dayOfMonth().getAsString());
467
468        attrs.addCDATAAttribute("next", ISODateTimeFormat.dateTime().print(nextDay));
469        attrs.addCDATAAttribute("nextYear", nextDay.year().getAsString());
470        attrs.addCDATAAttribute("nextMonth", nextDay.monthOfYear().getAsString());
471        attrs.addCDATAAttribute("nextDay", nextDay.dayOfMonth().getAsString());
472    }
473
474    /**
475     * Generate the list of selected tags.
476     * @param tags the list of tags.
477     * @throws SAXException if an error occurs while saxing
478     */
479    protected void _saxTags(Collection<String> tags) throws SAXException
480    {
481        XMLUtils.startElement(contentHandler, "tags");
482        for (String tag : tags)
483        {
484            AttributesImpl attrs = new AttributesImpl();
485            attrs.addCDATAAttribute("name", tag);
486            XMLUtils.createElement(contentHandler, "tag", attrs);
487        }
488        XMLUtils.endElement(contentHandler, "tags");
489    }
490
491    /**
492     * Generate the list of selected tags that act as categories and their descendant tags.
493     * @param categories the list of categories to generate.
494     * @throws SAXException if an error occurs while saxing
495     */
496    protected void _saxCategories(Collection<Tag> categories) throws SAXException
497    {
498        XMLUtils.startElement(contentHandler, "tag-categories");
499        for (Tag category : categories)
500        {
501            AttributesImpl categoryAttrs = new AttributesImpl();
502
503            XMLUtils.startElement(contentHandler, "category", categoryAttrs);
504
505            category.getTitle().toSAX(contentHandler, "title");
506
507            XMLUtils.startElement(contentHandler, "tags");
508            for (Tag tag : _eventsFilterHelper.getAllTags(category))
509            {
510                AttributesImpl tagAttrs = new AttributesImpl();
511                tagAttrs.addCDATAAttribute("name", tag.getName());
512                XMLUtils.startElement(contentHandler, "tag", tagAttrs);
513
514                tag.getTitle().toSAX(contentHandler);
515
516                XMLUtils.endElement(contentHandler, "tag");
517            }
518            XMLUtils.endElement(contentHandler, "tags");
519
520            XMLUtils.endElement(contentHandler, "category");
521        }
522        XMLUtils.endElement(contentHandler, "tag-categories");
523    }
524
525
526
527}