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}