Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License") + you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.openmeetings.service.calendar.caldav; import static java.util.UUID.randomUUID; import static org.apache.openmeetings.db.util.TimezoneUtil.getTimeZone; import java.net.URI; import java.text.ParsePosition; import java.util.ArrayList; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.NoSuchElementException; import java.util.Set; import java.util.TimeZone; import java.util.UUID; import org.apache.commons.lang3.time.FastDateFormat; import org.apache.openmeetings.db.dao.user.UserDao; import org.apache.openmeetings.db.entity.calendar.Appointment; import org.apache.openmeetings.db.entity.calendar.MeetingMember; import org.apache.openmeetings.db.entity.calendar.OmCalendar; import org.apache.openmeetings.db.entity.room.Room; import org.apache.openmeetings.db.entity.user.User; import org.apache.wicket.protocol.http.WebSession; import org.apache.wicket.util.string.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import net.fortuna.ical4j.model.Calendar; import net.fortuna.ical4j.model.Component; import net.fortuna.ical4j.model.ComponentList; import net.fortuna.ical4j.model.DateTime; import net.fortuna.ical4j.model.Parameter; import net.fortuna.ical4j.model.Property; import net.fortuna.ical4j.model.PropertyList; import net.fortuna.ical4j.model.Recur.Frequency; import net.fortuna.ical4j.model.TimeZoneRegistry; import net.fortuna.ical4j.model.TimeZoneRegistryFactory; import net.fortuna.ical4j.model.component.CalendarComponent; import net.fortuna.ical4j.model.component.VEvent; import net.fortuna.ical4j.model.parameter.Cn; import net.fortuna.ical4j.model.parameter.Role; import net.fortuna.ical4j.model.property.Attendee; import net.fortuna.ical4j.model.property.CalScale; import net.fortuna.ical4j.model.property.DateProperty; import net.fortuna.ical4j.model.property.Description; import net.fortuna.ical4j.model.property.Location; import net.fortuna.ical4j.model.property.Organizer; import net.fortuna.ical4j.model.property.ProdId; import net.fortuna.ical4j.model.property.Sequence; import net.fortuna.ical4j.model.property.Transp; import net.fortuna.ical4j.model.property.Uid; import net.fortuna.ical4j.model.property.Version; /** * Class which provides iCalendar Utilities. * This class's functions could be made static, as they are not instantiated anyway. */ @org.springframework.stereotype.Component public class IcalUtils { private static final Logger log = LoggerFactory.getLogger(IcalUtils.class); public static final String PROD_ID = "-//Events Calendar//Apache Openmeetings//EN"; @Autowired private UserDao userDao; /** * Parses the Calendar from the CalDAV server, to a new Appointment. * * @param calendar iCalendar Representation. * @param href Location of the Calendar on the server * @param etag ETag of the calendar. * @param omCalendar The Parent OmCalendar, to which the Appointment belongs. * @return Appointment after parsing. */ public Appointment parseCalendartoAppointment(Calendar calendar, String href, String etag, OmCalendar omCalendar) { //Note: By RFC 4791 only one event can be stored in one href. Appointment a = new Appointment(); a.setId(null); a.setDeleted(false); a.setHref(href); a.setCalendar(omCalendar); a.setOwner(omCalendar.getOwner()); a.setRoom(createDefaultRoom()); a.setReminder(Appointment.Reminder.none); return this.parseCalendartoAppointment(a, calendar, etag); } /** * Parses a Calendar with multiple VEvents into Appointments * * @param calendar Calendar to Parse * @param ownerId Owner of the Appointments * @return <code>List</code> of Appointments */ public List<Appointment> parseCalendartoAppointments(Calendar calendar, Long ownerId) { List<Appointment> appointments = new ArrayList<>(); ComponentList<CalendarComponent> events = calendar.getComponents(Component.VEVENT); User owner = userDao.get(ownerId); for (CalendarComponent event : events) { Appointment a = new Appointment(); a.setOwner(owner); a.setDeleted(false); a.setRoom(createDefaultRoom()); a.setReminder(Appointment.Reminder.none); a = addVEventPropertiestoAppointment(a, event); appointments.add(a); } return appointments; } /** * Updating Appointments which already exist, by parsing the Calendar. And updating etag. * Doesn't work with complex Recurrences. * Note: Hasn't been tested to acknowledge DST, timezones should acknowledge this. * * @param a Appointment to be updated. * @param calendar iCalendar Representation. * @param etag The ETag of the calendar. * @return Updated Appointment. */ public Appointment parseCalendartoAppointment(Appointment a, Calendar calendar, String etag) { if (calendar == null) { return a; } CalendarComponent event = calendar.getComponent(Component.VEVENT); if (event != null) { a.setEtag(etag); a = addVEventPropertiestoAppointment(a, event); } return a; } /** * Add properties from the Given VEvent Component to the Appointment * * @param a Appointment to which the properties are to be added * @param event VEvent to parse properties from. * @return Updated Appointment */ private Appointment addVEventPropertiestoAppointment(Appointment a, CalendarComponent event) { DateProperty dtstart = (DateProperty) event.getProperty(Property.DTSTART), dtend = (DateProperty) event.getProperty(Property.DTEND), dtstamp = (DateProperty) event.getProperty(Property.DTSTAMP), lastmod = (DateProperty) event.getProperty(Property.LAST_MODIFIED); Property uid = event.getProperty(Property.UID), description = event.getProperty(Property.DESCRIPTION), summary = event.getProperty(Property.SUMMARY), location = event.getProperty(Property.LOCATION), organizer = event.getProperty(Property.ORGANIZER), recur = event.getProperty(Property.RRULE); PropertyList<Attendee> attendees = event.getProperties(Property.ATTENDEE); if (uid != null) { a.setIcalId(uid.getValue()); } Date d = dtstart.getDate(); a.setStart(d); if (dtend == null) { a.setEnd(addTimetoDate(d, java.util.Calendar.HOUR_OF_DAY, 1)); } else { a.setEnd(dtend.getDate()); } a.setInserted(dtstamp.getDate()); if (lastmod != null) { a.setUpdated(lastmod.getDate()); } if (description != null) { a.setDescription(description.getValue()); } if (summary != null) { a.setTitle(summary.getValue()); } if (location != null) { a.setLocation(location.getValue()); } if (recur != null) { Parameter freq = recur.getParameter("FREQ"); if (freq != null) { if (freq.getValue().equals(Frequency.DAILY.name())) { a.setIsDaily(true); } else if (freq.getValue().equals(Frequency.WEEKLY.name())) { a.setIsWeekly(true); } else if (freq.getValue().equals(Frequency.MONTHLY.name())) { a.setIsMonthly(true); } else if (freq.getValue().equals(Frequency.YEARLY.name())) { a.setIsYearly(true); } } } Set<MeetingMember> attList = a.getMeetingMembers() == null ? new HashSet<>() : new HashSet<>(a.getMeetingMembers()); String organizerEmail = null; //Note this value can be repeated in attendees as well. if (organizer != null) { URI uri = URI.create(organizer.getValue()); //If the value of the organizer is an email if ("mailto".equals(uri.getScheme())) { String email = uri.getSchemeSpecificPart(); organizerEmail = email; if (!email.equals(a.getOwner().getAddress().getEmail())) { //Contact or exist and owner User org = userDao.getByEmail(email); if (org == null) { org = userDao.getContact(email, a.getOwner()); attList.add(createMeetingMember(a, org)); } else if (!org.getId().equals(a.getOwner().getId())) { attList.add(createMeetingMember(a, org)); } } } } if (attendees != null && !attendees.isEmpty()) { for (Property attendee : attendees) { URI uri = URI.create(attendee.getValue()); if ("mailto".equals(uri.getScheme())) { String email = uri.getSchemeSpecificPart(); Role role = attendee.getParameter(Role.CHAIR.getName()); if (role != null && role.getValue().equals(Role.CHAIR.getValue()) && email.equals(organizerEmail)) { continue; } User u = userDao.getByEmail(email); if (u == null) { u = userDao.getContact(email, a.getOwner()); } attList.add(createMeetingMember(a, u)); } } } a.setMeetingMembers(attList.isEmpty() ? null : new ArrayList<>(attList)); return a; } private static MeetingMember createMeetingMember(Appointment a, User u) { MeetingMember mm = new MeetingMember(); mm.setUser(u); mm.setDeleted(false); mm.setInserted(a.getInserted()); mm.setUpdated(a.getUpdated()); mm.setAppointment(a); return mm; } private static Room createDefaultRoom() { Room r = new Room(); r.setAppointment(true); if (r.getType() == null) { r.setType(Room.Type.conference); } return r; } /** * Parses the VTimezone Component of the given Calendar. If no, VTimezone component is found the * User Timezone is used * * @param calendar Calendar to parse * @param owner Owner of the Calendar * @return Parsed TimeZone */ public TimeZone parseTimeZone(Calendar calendar, User owner) { if (calendar != null) { Component timezone = calendar.getComponent(Component.VTIMEZONE); if (timezone != null) { Property tzid = timezone.getProperty(Property.TZID); if (tzid != null) { return getTimeZone(tzid.getValue()); } } } return getTimeZone(owner); } /** * Convenience function to parse date from {@link net.fortuna.ical4j.model.Property} to * {@link Date} * * @param dt DATE-TIME Property from which we parse. * @param timeZone Timezone of the Date. * @return {@link java.util.Date} representation of the iCalendar value. */ public Date parseDate(Property dt, TimeZone timeZone) { if (dt == null || Strings.isEmpty(dt.getValue())) { return null; } String[] acceptedFormats = { "yyyyMMdd'T'HHmmss", "yyyyMMdd'T'HHmmss'Z'", "yyyyMMdd" }; Parameter tzid = dt.getParameter(Parameter.TZID); if (tzid == null) { return parseDate(dt.getValue(), acceptedFormats, timeZone); } else { return parseDate(dt.getValue(), acceptedFormats, getTimeZone(tzid.getValue())); } } /** * Adapted from DateUtils to support Timezones, and parse ical dates into {@link java.util.Date}. * Note: Replace FastDateFormat to java.time, when shifting to Java 8 or higher. * * @param str Date representation in String. * @param patterns Patterns to parse the date against * @param _timeZone Timezone of the Date. * @return <code>java.util.Date</code> representation of string or * <code>null</code> if the Date could not be parsed. */ public Date parseDate(String str, String[] patterns, TimeZone _timeZone) { FastDateFormat parser; Locale locale = WebSession.get().getLocale(); TimeZone timeZone = str.endsWith("Z") ? TimeZone.getTimeZone("UTC") : _timeZone; ParsePosition pos = new ParsePosition(0); for (String pattern : patterns) { parser = FastDateFormat.getInstance(pattern, timeZone, locale); pos.setIndex(0); Date date = parser.parse(str, pos); if (date != null && pos.getIndex() == str.length()) { return date; } } log.error("Unable to parse the date: " + str + " at " + -1); return null; } /** * Adds a specified amount of time to a Date. * * @param date Date to which time is added * @param field Date Field to which the Amount is added * @param amount Amount to be Added * @return New Date */ public Date addTimetoDate(Date date, int field, int amount) { java.util.Calendar c = java.util.Calendar.getInstance(); c.setTime(date); c.add(field, amount); return c.getTime(); } /** * Methods to parse Appointment to iCalendar according RFC 2445 * * @param appointment to be converted to iCalendar * @return iCalendar representation of the Appointment */ public Calendar parseAppointmenttoCalendar(Appointment appointment) { String tzid = parseTimeZone(null, appointment.getOwner()).getID(); TimeZoneRegistry registry = TimeZoneRegistryFactory.getInstance().createRegistry(); net.fortuna.ical4j.model.TimeZone timeZone = registry.getTimeZone(tzid); if (timeZone == null) { throw new NoSuchElementException("Unable to get time zone by id provided: " + tzid); } Calendar icsCalendar = new Calendar(); icsCalendar.getProperties().add(new ProdId("-//Events Calendar//Apache Openmeetings//EN")); icsCalendar.getProperties().add(Version.VERSION_2_0); icsCalendar.getProperties().add(CalScale.GREGORIAN); icsCalendar.getComponents().add(timeZone.getVTimeZone()); DateTime start = new DateTime(appointment.getStart()), end = new DateTime(appointment.getEnd()); VEvent meeting = new VEvent(start, end, appointment.getTitle()); meeting = addVEventpropsfromAppointment(appointment, meeting); icsCalendar.getComponents().add(meeting); return icsCalendar; } /** * Adds the Appointment Properties to the given VEvent * * @param appointment Appointment whose properties are taken * @param meeting VEvent of the Appointment * @return Updated VEvent */ private static VEvent addVEventpropsfromAppointment(Appointment appointment, VEvent meeting) { if (appointment.getLocation() != null) { meeting.getProperties().add(new Location(appointment.getLocation())); } meeting.getProperties().add(new Description(appointment.getDescription())); meeting.getProperties().add(new Sequence(0)); meeting.getProperties().add(Transp.OPAQUE); String uid = appointment.getIcalId(); Uid ui; if (uid == null || uid.length() < 1) { UUID uuid = randomUUID(); appointment.setIcalId(uuid.toString()); ui = new Uid(uuid.toString()); } else { ui = new Uid(uid); } meeting.getProperties().add(ui); if (appointment.getMeetingMembers() != null) { for (MeetingMember meetingMember : appointment.getMeetingMembers()) { Attendee attendee = new Attendee( URI.create("mailto:" + meetingMember.getUser().getAddress().getEmail())); attendee.getParameters().add(Role.REQ_PARTICIPANT); attendee.getParameters().add(new Cn(meetingMember.getUser().getLogin())); meeting.getProperties().add(attendee); } } URI orgUri = URI.create("mailto:" + appointment.getOwner().getAddress().getEmail()); Attendee orgAtt = new Attendee(orgUri); orgAtt.getParameters().add(Role.CHAIR); Cn orgCn = new Cn(appointment.getOwner().getLogin()); orgAtt.getParameters().add(orgCn); meeting.getProperties().add(orgAtt); Organizer organizer = new Organizer(orgUri); organizer.getParameters().add(orgCn); meeting.getProperties().add(organizer); return meeting; } /** * Parses a List of Appointments into a VCALENDAR component. * * @param appointments List of Appointments for the Calendar * @param ownerId Owner of the Appointments * @return VCALENDAR representation of the Appointments */ public Calendar parseAppointmentstoCalendar(List<Appointment> appointments, Long ownerId) { String tzid = parseTimeZone(null, userDao.get(ownerId)).getID(); TimeZoneRegistry registry = TimeZoneRegistryFactory.getInstance().createRegistry(); net.fortuna.ical4j.model.TimeZone timeZone = registry.getTimeZone(tzid); if (timeZone == null) { throw new NoSuchElementException("Unable to get time zone by id provided: " + tzid); } Calendar icsCalendar = new Calendar(); icsCalendar.getProperties().add(new ProdId(PROD_ID)); icsCalendar.getProperties().add(Version.VERSION_2_0); icsCalendar.getProperties().add(CalScale.GREGORIAN); icsCalendar.getComponents().add(timeZone.getVTimeZone()); for (Appointment appointment : appointments) { DateTime start = new DateTime(appointment.getStart()), end = new DateTime(appointment.getEnd()); VEvent meeting = new VEvent(start, end, appointment.getTitle()); meeting = addVEventpropsfromAppointment(appointment, meeting); icsCalendar.getComponents().add(meeting); } return icsCalendar; } }