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.Url; 051 052/** 053 * Helper from ICS events 054 */ 055public class IcsEventHelper implements Component, Serviceable 056{ 057 /** The component role. */ 058 public static final String ROLE = IcsEventHelper.class.getName(); 059 060 private IcsReader _icsReader; 061 private TagProviderExtensionPoint _tagProviderEP; 062 063 public void service(ServiceManager smanager) throws ServiceException 064 { 065 _icsReader = (IcsReader) smanager.lookup(IcsReader.ROLE); 066 _tagProviderEP = (TagProviderExtensionPoint) smanager.lookup(TagProviderExtensionPoint.ROLE); 067 } 068 069 /** 070 * Read the configured distant ics sources into a list of {@link IcsEvents} 071 * @param zoneItem zoneItem where the configuration will be fetched 072 * @param siteName name of the current site 073 * @param dateRange range of dates to limit 074 * @return a list of {@link IcsEvents} 075 */ 076 public List<IcsEvents> getICSEvents(ZoneItem zoneItem, String siteName, EventsFilterHelper.DateTimeRange dateRange) 077 { 078 List<IcsEvents> icsEvents = new ArrayList<>(); 079 if (zoneItem != null && zoneItem.getServiceParameters().hasValue("ics")) 080 { 081 Long nbIcsEvent = zoneItem.getServiceParameters().getValue("nbEvents"); 082 Long maxIcsSize = zoneItem.getServiceParameters().getValue("maxSize"); 083 084 ModifiableModelAwareRepeater icsRepeater = zoneItem.getServiceParameters().getValue("ics"); 085 for (ModifiableModelAwareRepeaterEntry repeaterEntry : icsRepeater.getEntries()) 086 { 087 String url = repeaterEntry.getValue("url"); 088 IcsEvents eventList = _icsReader.getEventList(url, dateRange, nbIcsEvent, maxIcsSize); 089 090 String tagName = repeaterEntry.getValue("tag"); 091 if (StringUtils.isNotEmpty(tagName)) 092 { 093 Tag tag = _tagProviderEP.getTag(tagName, Map.of("siteName", siteName)); 094 eventList.setTag(tag); 095 } 096 097 icsEvents.add(eventList); 098 } 099 } 100 return icsEvents; 101 } 102 103 /** 104 * Get the ICS tags from search service 105 * @param zoneItem The zone item id 106 * @param siteName the site name 107 * @return the ICS tags 108 */ 109 public Set<Tag> getIcsTags(ZoneItem zoneItem, String siteName) 110 { 111 Set<Tag> tags = new LinkedHashSet<>(); 112 113 if (zoneItem != null && zoneItem.getServiceParameters().hasValue("ics")) 114 { 115 ModifiableModelAwareRepeater icsRepeater = zoneItem.getServiceParameters().getValue("ics"); 116 for (ModifiableModelAwareRepeaterEntry repeaterEntry : icsRepeater.getEntries()) 117 { 118 String tagName = repeaterEntry.getValue("tag"); 119 Tag tag = _tagProviderEP.getTag(tagName, Map.of("siteName", siteName)); 120 if (tag != null) 121 { 122 tags.add(tag); 123 } 124 } 125 } 126 127 return tags; 128 } 129 130 /** 131 * Get a list of {@link LocalVEvent} form the list of {@link IcsEvents} 132 * @param icsEventsList the list of {@link IcsEvents} 133 * @param dateRange range of dates to limit 134 * @return a list of {@link LocalVEvent} 135 */ 136 public Pair<List<LocalVEvent>, String> toLocalIcsEvent(List<IcsEvents> icsEventsList, EventsFilterHelper.DateTimeRange dateRange) 137 { 138 return toLocalIcsEvent(icsEventsList, dateRange, List.of()); 139 } 140 141 /** 142 * Get a list of {@link LocalVEvent} form the list of {@link IcsEvents} 143 * @param icsEventsList the list of {@link IcsEvents} 144 * @param dateRange range of dates to limit 145 * @param filteredTags A list of tag's name to filter ICS events. Can be empty to no filter on tags. 146 * @return a list of {@link LocalVEvent} 147 */ 148 public Pair<List<LocalVEvent>, String> toLocalIcsEvent(List<IcsEvents> icsEventsList, EventsFilterHelper.DateTimeRange dateRange, List<String> filteredTags) 149 { 150 List<LocalVEvent> localICSEvents = new ArrayList<>(); 151 String fullICSDistantEvents = ""; 152 for (IcsEvents icsEvents : icsEventsList) 153 { 154 if (icsEvents.hasEvents()) 155 { 156 Tag tag = icsEvents.getTag(); 157 if (filteredTags.isEmpty() || tag != null && filteredTags.contains(tag.getName())) 158 { 159 for (VEvent calendarComponent : icsEvents.getEvents()) 160 { 161 List<LocalVEvent> localCalendarComponents = _icsReader.getEventDates(calendarComponent, dateRange, tag); 162 localICSEvents.addAll(localCalendarComponents); 163 if (dateRange == null) 164 { 165 // 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 166 fullICSDistantEvents += calendarComponent.toString() + "\n"; 167 } 168 } 169 } 170 } 171 } 172 173 return new ImmutablePair<>(localICSEvents, fullICSDistantEvents); 174 } 175 176 /** 177 * SAX ics events hits 178 * @param handler the content handler 179 * @param icsEvents The ics events 180 * @param startNumber the start index 181 * @throws SAXException if an error occurred while saxing 182 */ 183 public void saxIcsEventHits(ContentHandler handler, List<LocalVEvent> icsEvents, int startNumber) throws SAXException 184 { 185 int hitIndex = startNumber; 186 for (LocalVEvent icsEvent : icsEvents) 187 { 188 saxIcsEventHit(handler, icsEvent, hitIndex++); 189 } 190 } 191 192 /** 193 * SAX a ics events hit 194 * @param handler the content handler 195 * @param icsEvent The ics event 196 * @param number the hit index 197 * @throws SAXException if an error occurred while saxing 198 */ 199 public void saxIcsEventHit(ContentHandler handler, LocalVEvent icsEvent, int number) throws SAXException 200 { 201 VEvent event = icsEvent.getEvent(); 202 203 AttributesImpl attrs = new AttributesImpl(); 204 attrs.addCDATAAttribute("number", Integer.toString(number)); 205 attrs.addCDATAAttribute("icsEvent", "true"); 206 XMLUtils.startElement(handler, "hit", attrs); 207 208 String id = event.getProperty(Property.UID) != null ? event.getProperty(Property.UID).getValue() : UUID.randomUUID().toString(); 209 XMLUtils.createElement(handler, "id", id); 210 211 saxIcsEvent(handler, icsEvent); 212 213 XMLUtils.endElement(handler, "hit"); 214 } 215 216 /** 217 * SAX a ics event 218 * @param handler the content handler 219 * @param icsEvent The ics event 220 * @throws SAXException if an error occurred while saxing 221 */ 222 public void saxIcsEvent(ContentHandler handler, LocalVEvent icsEvent) throws SAXException 223 { 224 XMLUtils.startElement(handler, "event"); 225 226 VEvent event = icsEvent.getEvent(); 227 228 String title = event.getProperty(Property.SUMMARY) != null ? event.getProperty(Property.SUMMARY).getValue() : StringUtils.EMPTY; 229 XMLUtils.createElement(handler, "title", title); 230 231 String description = event.getProperty(Property.DESCRIPTION) != null ? event.getProperty(Property.DESCRIPTION).getValue() : StringUtils.EMPTY; 232 XMLUtils.createElement(handler, "description", description); 233 234 Date dtStamp = event.getDateStamp() != null ? event.getDateStamp().getDate() : new Date(); 235 Date creationDate = event.getCreated() != null ? event.getCreated().getDate() : dtStamp; 236 Date lastModifiedDate = event.getLastModified() != null ? event.getLastModified().getDate() : dtStamp; 237 238 _saxDate(handler, "creationDate", creationDate); 239 _saxDate(handler, "lastModifiedDate", lastModifiedDate); 240 241 _saxDate(handler, "startDate", icsEvent.getStart()); 242 _saxDate(handler, "endDate", icsEvent.getEnd()); 243 244 CalendarSearchService.saxTag(handler, icsEvent.getTag()); 245 246 Url url = event.getUrl(); 247 if (url != null) 248 { 249 XMLUtils.createElement(handler, "url", url.getValue()); 250 } 251 252 XMLUtils.endElement(handler, "event"); 253 254 } 255 256 private void _saxDate(ContentHandler handler, String tagName, Date date) throws SAXException 257 { 258 if (date != null) 259 { 260 XMLUtils.createElement(handler, tagName, DateUtils.dateToString(date)); 261 } 262 } 263}