001/* 002 * Copyright 2022 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.icsreader; 017 018import java.util.ArrayList; 019import java.util.Date; 020import java.util.LinkedHashSet; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024import java.util.UUID; 025 026import org.apache.avalon.framework.component.Component; 027import org.apache.avalon.framework.service.ServiceException; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.apache.avalon.framework.service.Serviceable; 030import org.apache.cocoon.xml.AttributesImpl; 031import org.apache.cocoon.xml.XMLUtils; 032import org.apache.commons.lang3.StringUtils; 033import org.apache.commons.lang3.tuple.ImmutablePair; 034import org.apache.commons.lang3.tuple.Pair; 035import org.xml.sax.ContentHandler; 036import org.xml.sax.SAXException; 037 038import org.ametys.cms.tag.Tag; 039import org.ametys.cms.tag.TagProviderExtensionPoint; 040import org.ametys.core.util.DateUtils; 041import org.ametys.plugins.calendar.events.EventsFilterHelper; 042import org.ametys.plugins.calendar.icsreader.IcsReader.IcsEvents; 043import org.ametys.plugins.calendar.search.CalendarSearchService; 044import org.ametys.plugins.repository.data.holder.group.ModifiableModelAwareRepeater; 045import org.ametys.plugins.repository.data.holder.group.ModifiableModelAwareRepeaterEntry; 046import org.ametys.web.repository.page.ZoneItem; 047 048import net.fortuna.ical4j.model.Property; 049import net.fortuna.ical4j.model.component.VEvent; 050import net.fortuna.ical4j.model.property.DtEnd; 051import net.fortuna.ical4j.model.property.DtStart; 052import net.fortuna.ical4j.model.property.Url; 053 054/** 055 * Helper from ICS events 056 */ 057public class IcsEventHelper implements Component, Serviceable 058{ 059 /** The component role. */ 060 public static final String ROLE = IcsEventHelper.class.getName(); 061 062 /** The ICS reader */ 063 protected IcsReader _icsReader; 064 /** The tag provider extention point */ 065 protected TagProviderExtensionPoint _tagProviderEP; 066 067 public void service(ServiceManager smanager) throws ServiceException 068 { 069 _icsReader = (IcsReader) smanager.lookup(IcsReader.ROLE); 070 _tagProviderEP = (TagProviderExtensionPoint) smanager.lookup(TagProviderExtensionPoint.ROLE); 071 } 072 073 /** 074 * Read the configured distant ics sources into a list of {@link IcsEvents} 075 * @param zoneItem zoneItem where the configuration will be fetched 076 * @param siteName name of the current site 077 * @param dateRange range of dates to limit 078 * @return a list of {@link IcsEvents} 079 */ 080 public List<IcsEvents> getICSEvents(ZoneItem zoneItem, String siteName, EventsFilterHelper.DateTimeRange dateRange) 081 { 082 List<IcsEvents> icsEvents = new ArrayList<>(); 083 if (zoneItem != null && zoneItem.getServiceParameters().hasValue("ics")) 084 { 085 Long nbIcsEvent = zoneItem.getServiceParameters().getValue("nbEvents"); 086 Long maxIcsSize = zoneItem.getServiceParameters().getValue("maxSize"); 087 088 ModifiableModelAwareRepeater icsRepeater = zoneItem.getServiceParameters().getValue("ics"); 089 for (ModifiableModelAwareRepeaterEntry repeaterEntry : icsRepeater.getEntries()) 090 { 091 String url = repeaterEntry.getValue("url"); 092 IcsEvents eventList = _icsReader.getEventList(url, dateRange, nbIcsEvent, maxIcsSize); 093 094 String tagName = repeaterEntry.getValue("tag"); 095 if (StringUtils.isNotEmpty(tagName)) 096 { 097 Tag tag = _tagProviderEP.getTag(tagName, Map.of("siteName", siteName)); 098 eventList.setTag(tag); 099 } 100 101 icsEvents.add(eventList); 102 } 103 } 104 return icsEvents; 105 } 106 107 /** 108 * Get the ICS tags from search service 109 * @param zoneItem The zone item id 110 * @param siteName the site name 111 * @return the ICS tags 112 */ 113 public Set<Tag> getIcsTags(ZoneItem zoneItem, String siteName) 114 { 115 Set<Tag> tags = new LinkedHashSet<>(); 116 117 if (zoneItem != null && zoneItem.getServiceParameters().hasValue("ics")) 118 { 119 ModifiableModelAwareRepeater icsRepeater = zoneItem.getServiceParameters().getValue("ics"); 120 for (ModifiableModelAwareRepeaterEntry repeaterEntry : icsRepeater.getEntries()) 121 { 122 String tagName = repeaterEntry.getValue("tag"); 123 Tag tag = _tagProviderEP.getTag(tagName, Map.of("siteName", siteName)); 124 if (tag != null) 125 { 126 tags.add(tag); 127 } 128 } 129 } 130 131 return tags; 132 } 133 134 /** 135 * Get a list of {@link LocalVEvent} form the list of {@link IcsEvents} 136 * @param icsEventsList the list of {@link IcsEvents} 137 * @param dateRange range of dates to limit 138 * @return a list of {@link LocalVEvent} 139 */ 140 public Pair<List<LocalVEvent>, String> toLocalIcsEvent(List<IcsEvents> icsEventsList, EventsFilterHelper.DateTimeRange dateRange) 141 { 142 return toLocalIcsEvent(icsEventsList, dateRange, List.of()); 143 } 144 145 /** 146 * Get a list of {@link LocalVEvent} form the list of {@link IcsEvents} 147 * @param icsEventsList the list of {@link IcsEvents} 148 * @param dateRange range of dates to limit 149 * @param filteredTags A list of tag's name to filter ICS events. Can be empty to no filter on tags. 150 * @return a list of {@link LocalVEvent} 151 */ 152 public Pair<List<LocalVEvent>, String> toLocalIcsEvent(List<IcsEvents> icsEventsList, EventsFilterHelper.DateTimeRange dateRange, List<String> filteredTags) 153 { 154 List<LocalVEvent> localICSEvents = new ArrayList<>(); 155 String fullICSDistantEvents = ""; 156 for (IcsEvents icsEvents : icsEventsList) 157 { 158 if (icsEvents.hasEvents()) 159 { 160 Tag tag = icsEvents.getTag(); 161 if (filteredTags.isEmpty() || tag != null && filteredTags.contains(tag.getName())) 162 { 163 for (VEvent calendarComponent : icsEvents.getEvents()) 164 { 165 List<LocalVEvent> localCalendarComponents = _icsReader.getEventDates(calendarComponent, dateRange, tag); 166 localICSEvents.addAll(localCalendarComponents); 167 if (dateRange == null) 168 { 169 // To avoid to have the ICS events in double in some exotic non-anticipated cases, when there is a dateRange, we do not copy the ICS 170 fullICSDistantEvents += calendarComponent.toString() + "\n"; 171 } 172 } 173 } 174 } 175 } 176 177 return new ImmutablePair<>(localICSEvents, fullICSDistantEvents); 178 } 179 180 /** 181 * SAX ics events hits 182 * @param handler the content handler 183 * @param icsEvents The ics events 184 * @param startNumber the start index 185 * @throws SAXException if an error occurred while saxing 186 */ 187 public void saxIcsEventHits(ContentHandler handler, List<LocalVEvent> icsEvents, int startNumber) throws SAXException 188 { 189 int hitIndex = startNumber; 190 for (LocalVEvent icsEvent : icsEvents) 191 { 192 saxIcsEventHit(handler, icsEvent, hitIndex++); 193 } 194 } 195 196 /** 197 * SAX a ics events hit 198 * @param handler the content handler 199 * @param icsEvent The ics event 200 * @param number the hit index 201 * @throws SAXException if an error occurred while saxing 202 */ 203 public void saxIcsEventHit(ContentHandler handler, LocalVEvent icsEvent, int number) throws SAXException 204 { 205 VEvent event = icsEvent.getEvent(); 206 207 AttributesImpl attrs = new AttributesImpl(); 208 attrs.addCDATAAttribute("number", Integer.toString(number)); 209 attrs.addCDATAAttribute("icsEvent", "true"); 210 XMLUtils.startElement(handler, "hit", attrs); 211 212 String id = event.getProperty(Property.UID) != null ? event.getProperty(Property.UID).getValue() : UUID.randomUUID().toString(); 213 XMLUtils.createElement(handler, "id", id); 214 215 saxIcsEvent(handler, icsEvent); 216 217 XMLUtils.endElement(handler, "hit"); 218 } 219 220 /** 221 * SAX a ics event 222 * @param handler the content handler 223 * @param icsEvent The ics event 224 * @throws SAXException if an error occurred while saxing 225 */ 226 public void saxIcsEvent(ContentHandler handler, LocalVEvent icsEvent) throws SAXException 227 { 228 XMLUtils.startElement(handler, "event"); 229 230 VEvent event = icsEvent.getEvent(); 231 232 String title = event.getProperty(Property.SUMMARY) != null ? event.getProperty(Property.SUMMARY).getValue() : StringUtils.EMPTY; 233 XMLUtils.createElement(handler, "title", title); 234 235 String description = event.getProperty(Property.DESCRIPTION) != null ? event.getProperty(Property.DESCRIPTION).getValue() : StringUtils.EMPTY; 236 XMLUtils.createElement(handler, "description", description); 237 238 Date dtStamp = event.getDateStamp() != null ? event.getDateStamp().getDate() : new Date(); 239 Date creationDate = event.getCreated() != null ? event.getCreated().getDate() : dtStamp; 240 Date lastModifiedDate = event.getLastModified() != null ? event.getLastModified().getDate() : dtStamp; 241 242 _saxDate(handler, "creationDate", creationDate); 243 _saxDate(handler, "lastModifiedDate", lastModifiedDate); 244 245 _saxDate(handler, "startDate", icsEvent.getStart()); 246 _saxDate(handler, "endDate", icsEvent.getEnd()); 247 248 _saxAllDay(handler, icsEvent); 249 250 CalendarSearchService.saxTag(handler, icsEvent.getTag()); 251 252 Url url = event.getUrl(); 253 if (url != null) 254 { 255 XMLUtils.createElement(handler, "url", url.getValue()); 256 } 257 258 XMLUtils.endElement(handler, "event"); 259 260 } 261 262 /** 263 * Sax all day property 264 * @param handler the content handler 265 * @param icsEvent the ICS event 266 * @throws SAXException if an error occurred while saxing 267 */ 268 protected void _saxAllDay(ContentHandler handler, LocalVEvent icsEvent) throws SAXException 269 { 270 VEvent event = icsEvent.getEvent(); 271 272 DtStart startDate = event.getStartDate(); 273 DtEnd endDate = event.getEndDate(); 274 275 boolean allDayEvent = startDate != null && startDate.getParameter("VALUE") != null && "DATE".equals(startDate.getParameter("VALUE").getValue()) 276 && (endDate == null 277 || endDate.getParameter("VALUE") != null && "DATE".equals(endDate.getParameter("VALUE").getValue()) 278 ); 279 280 XMLUtils.createElement(handler, "allDay", String.valueOf(allDayEvent)); 281 } 282 283 /** 284 * Sax ICS date 285 * @param handler the content handler 286 * @param tagName the xml tag name 287 * @param date the date to sax 288 * @throws SAXException if an error occurred while saxing 289 */ 290 protected void _saxDate(ContentHandler handler, String tagName, Date date) throws SAXException 291 { 292 if (date != null) 293 { 294 XMLUtils.createElement(handler, tagName, DateUtils.dateToString(date)); 295 } 296 } 297}