/******************************************************************************* * 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.ofbiz.workeffort.workeffort; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.net.URISyntaxException; import java.sql.Timestamp; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import net.fortuna.ical4j.data.CalendarBuilder; import net.fortuna.ical4j.data.ParserException; 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.Dur; import net.fortuna.ical4j.model.Parameter; import net.fortuna.ical4j.model.ParameterList; import net.fortuna.ical4j.model.Property; import net.fortuna.ical4j.model.PropertyList; import net.fortuna.ical4j.model.TimeZoneRegistry; import net.fortuna.ical4j.model.TimeZoneRegistryFactory; import net.fortuna.ical4j.model.ValidationException; import net.fortuna.ical4j.model.component.VAlarm; import net.fortuna.ical4j.model.component.VEvent; import net.fortuna.ical4j.model.component.VToDo; import net.fortuna.ical4j.model.parameter.Cn; import net.fortuna.ical4j.model.parameter.PartStat; import net.fortuna.ical4j.model.parameter.XParameter; import net.fortuna.ical4j.model.property.Action; import net.fortuna.ical4j.model.property.Attendee; import net.fortuna.ical4j.model.property.CalScale; import net.fortuna.ical4j.model.property.Clazz; import net.fortuna.ical4j.model.property.Completed; import net.fortuna.ical4j.model.property.Created; import net.fortuna.ical4j.model.property.Description; import net.fortuna.ical4j.model.property.DtEnd; import net.fortuna.ical4j.model.property.DtStamp; import net.fortuna.ical4j.model.property.DtStart; import net.fortuna.ical4j.model.property.Duration; import net.fortuna.ical4j.model.property.LastModified; import net.fortuna.ical4j.model.property.Location; import net.fortuna.ical4j.model.property.Organizer; import net.fortuna.ical4j.model.property.PercentComplete; import net.fortuna.ical4j.model.property.Priority; import net.fortuna.ical4j.model.property.ProdId; import net.fortuna.ical4j.model.property.Status; import net.fortuna.ical4j.model.property.Summary; import net.fortuna.ical4j.model.property.Uid; import net.fortuna.ical4j.model.property.Version; import net.fortuna.ical4j.model.property.XProperty; import org.apache.ofbiz.base.util.DateRange; import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.ObjectType; import org.apache.ofbiz.base.util.TimeDuration; import org.apache.ofbiz.base.util.UtilGenerics; import org.apache.ofbiz.base.util.UtilMisc; import org.apache.ofbiz.base.util.UtilProperties; import org.apache.ofbiz.base.util.UtilValidate; import org.apache.ofbiz.entity.Delegator; import org.apache.ofbiz.entity.GenericEntityException; import org.apache.ofbiz.entity.GenericValue; import org.apache.ofbiz.entity.util.EntityQuery; import org.apache.ofbiz.service.GenericServiceException; import org.apache.ofbiz.service.LocalDispatcher; import org.apache.ofbiz.service.ModelParam; import org.apache.ofbiz.service.ModelService; import org.apache.ofbiz.service.ServiceUtil; import org.apache.ofbiz.service.calendar.TemporalExpression; import org.apache.ofbiz.service.calendar.TemporalExpressionWorker; import org.apache.ofbiz.workeffort.workeffort.ICalWorker.ResponseProperties; /** iCalendar converter class. This class uses the <a href="http://ical4j.sourceforge.net/index.html"> * iCal4J</a> library. */ public class ICalConverter { protected static final String module = ICalConverter.class.getName(); protected static final String partyIdXParamName = "X-ORG-APACHE-OFBIZ-PARTY-ID"; protected static final ProdId prodId = new ProdId("-//Apache OFBiz//Work Effort Calendar//EN"); protected static final String uidPrefix = "ORG-APACHE-OFBIZ-WE-"; protected static final String workEffortIdXPropName = "X-ORG-APACHE-OFBIZ-WORKEFFORT-ID"; protected static final String reminderXPropName = "X-ORG-APACHE-OFBIZ-REMINDER-ID"; protected static final Map<String, String> fromStatusMap = UtilMisc.toMap("TENTATIVE", "CAL_TENTATIVE", "CONFIRMED", "CAL_CONFIRMED", "CANCELLED", "CAL_CANCELLED", "NEEDS-ACTION", "CAL_NEEDS_ACTION", "COMPLETED", "CAL_COMPLETED", "IN-PROCESS", "CAL_ACCEPTED"); protected static final Map<String, Status> toStatusMap = UtilMisc.toMap("CAL_TENTATIVE", Status.VEVENT_TENTATIVE, "CAL_CONFIRMED", Status.VEVENT_CONFIRMED, "CAL_CANCELLED", Status.VEVENT_CANCELLED, "CAL_NEEDS_ACTION", Status.VTODO_NEEDS_ACTION, "CAL_COMPLETED", Status.VTODO_COMPLETED, "CAL_ACCEPTED", Status.VTODO_IN_PROCESS); protected static final Map<String, PartStat> toPartStatusMap = UtilMisc.toMap( "PRTYASGN_OFFERED", PartStat.TENTATIVE, "PRTYASGN_ASSIGNED", PartStat.ACCEPTED); protected static final Map<String, String> fromPartStatusMap = UtilMisc.toMap( "TENTATIVE", "PRTYASGN_OFFERED", "ACCEPTED", "PRTYASGN_ASSIGNED"); protected static final Map<String, String> fromRoleMap = UtilMisc.toMap("ATTENDEE", "CAL_ATTENDEE", "CONTACT", "CONTACT", "ORGANIZER", "CAL_ORGANIZER"); protected static VAlarm createAlarm(GenericValue workEffortEventReminder) { VAlarm alarm = null; Timestamp reminderStamp = workEffortEventReminder.getTimestamp("reminderDateTime"); if (reminderStamp != null) { alarm = new VAlarm(new DateTime(reminderStamp)); } else { TimeDuration duration = TimeDuration.fromNumber(workEffortEventReminder.getLong("reminderOffset")); alarm = new VAlarm(new Dur(duration.days(), duration.hours(), duration.minutes(), duration.seconds())); } return alarm; } protected static Attendee createAttendee(GenericValue partyValue, Map<String, Object> context) { Attendee attendee = new Attendee(); loadPartyAssignment(attendee, partyValue, context); return attendee; } protected static Organizer createOrganizer(GenericValue partyValue, Map<String, Object> context) { Organizer organizer = new Organizer(); loadPartyAssignment(organizer, partyValue, context); return organizer; } protected static ResponseProperties createWorkEffort(Component component, Map<String, Object> context) { Map<String, Object> serviceMap = new HashMap<String, Object>(); setWorkEffortServiceMap(component, serviceMap); serviceMap.put("workEffortTypeId", "VTODO".equals(component.getName()) ? "TASK" : "EVENT"); serviceMap.put("currentStatusId", "VTODO".equals(component.getName()) ? "CAL_NEEDS_ACTION" : "CAL_TENTATIVE"); serviceMap.put("partyId", ((GenericValue) context.get("userLogin")).get("partyId")); serviceMap.put("roleTypeId", "CAL_OWNER"); serviceMap.put("statusId", "PRTYASGN_ASSIGNED"); Map<String, Object> serviceResult = invokeService("createWorkEffortAndPartyAssign", serviceMap, context); if (ServiceUtil.isError(serviceResult)) { return ICalWorker.createPartialContentResponse(ServiceUtil.getErrorMessage(serviceResult)); } String workEffortId = (String) serviceResult.get("workEffortId"); if (workEffortId != null) { replaceProperty(component.getProperties(), toXProperty(workEffortIdXPropName, workEffortId)); serviceMap.clear(); serviceMap.put("workEffortIdFrom", context.get("workEffortId")); serviceMap.put("workEffortIdTo", workEffortId); serviceMap.put("workEffortAssocTypeId", "WORK_EFF_DEPENDENCY"); serviceMap.put("fromDate", new Timestamp(System.currentTimeMillis())); serviceResult = invokeService("createWorkEffortAssoc", serviceMap, context); if (ServiceUtil.isError(serviceResult)) { return ICalWorker.createPartialContentResponse(ServiceUtil.getErrorMessage(serviceResult)); } storePartyAssignments(workEffortId, component, context); } return null; } protected static String fromClazz(PropertyList propertyList) { Clazz iCalObj = (Clazz) propertyList.getProperty(Clazz.CLASS); if (iCalObj == null) { return null; } return "WES_".concat(iCalObj.getValue()); } protected static Timestamp fromCompleted(PropertyList propertyList) { Completed iCalObj = (Completed) propertyList.getProperty(Completed.COMPLETED); if (iCalObj == null) { return null; } Date date = iCalObj.getDate(); return new Timestamp(date.getTime()); } protected static String fromDescription(PropertyList propertyList) { Description iCalObj = (Description) propertyList.getProperty(Description.DESCRIPTION); if (iCalObj == null) { return null; } return iCalObj.getValue(); } protected static Timestamp fromDtEnd(PropertyList propertyList) { DtEnd iCalObj = (DtEnd) propertyList.getProperty(DtEnd.DTEND); if (iCalObj == null) { return null; } Date date = iCalObj.getDate(); return new Timestamp(date.getTime()); } protected static Timestamp fromDtStart(PropertyList propertyList) { DtStart iCalObj = (DtStart) propertyList.getProperty(DtStart.DTSTART); if (iCalObj == null) { return null; } Date date = iCalObj.getDate(); return new Timestamp(date.getTime()); } protected static Double fromDuration(PropertyList propertyList) { Duration iCalObj = (Duration) propertyList.getProperty(Duration.DURATION); if (iCalObj == null) { return null; } Dur dur = iCalObj.getDuration(); TimeDuration td = new TimeDuration(0, 0, (dur.getWeeks() * 7) + dur.getDays(), dur.getHours(), dur.getMinutes(), dur.getSeconds(), 0); return new Double(TimeDuration.toLong(td)); } protected static Timestamp fromLastModified(PropertyList propertyList) { LastModified iCalObj = (LastModified) propertyList.getProperty(LastModified.LAST_MODIFIED); if (iCalObj == null) { return null; } Date date = iCalObj.getDate(); return new Timestamp(date.getTime()); } protected static String fromLocation(PropertyList propertyList) { Location iCalObj = (Location) propertyList.getProperty(Location.LOCATION); if (iCalObj == null) { return null; } return iCalObj.getValue(); } protected static String fromParticipationStatus(Parameter status) { if (status == null) { return null; } return fromPartStatusMap.get(status.getValue()); } protected static Long fromPercentComplete(PropertyList propertyList) { PercentComplete iCalObj = (PercentComplete) propertyList.getProperty(PercentComplete.PERCENT_COMPLETE); if (iCalObj == null) { return null; } return new Long(iCalObj.getPercentage()); } protected static Double fromPriority(PropertyList propertyList) { Priority iCalObj = (Priority) propertyList.getProperty(Priority.PRIORITY); if (iCalObj == null) { return null; } return new Double(iCalObj.getLevel()); } protected static String fromStatus(PropertyList propertyList) { Status iCalObj = (Status) propertyList.getProperty(Status.STATUS); if (iCalObj == null) { return null; } return fromStatusMap.get(iCalObj.getValue()); } protected static String fromSummary(PropertyList propertyList) { Summary iCalObj = (Summary) propertyList.getProperty(Summary.SUMMARY); if (iCalObj == null) { return null; } return iCalObj.getValue(); } protected static String fromUid(PropertyList propertyList) { Uid iCalObj = (Uid) propertyList.getProperty(Uid.UID); if (iCalObj == null) { return null; } return iCalObj.getValue(); } protected static String fromXParameter(ParameterList parameterList, String parameterName) { if (parameterName == null) { return null; } Parameter parameter = parameterList.getParameter(parameterName); if (parameter != null) { return parameter.getValue(); } return null; } protected static String fromXProperty(PropertyList propertyList, String propertyName) { if (propertyName == null) { return null; } Property property = propertyList.getProperty(propertyName); if (property != null) { return property.getValue(); } return null; } protected static void getAlarms(GenericValue workEffort, ComponentList alarms) throws GenericEntityException { Description description = null; if (workEffort.get("description") != null) { description = new Description(workEffort.getString("description")); } else { description = new Description(workEffort.getString("workEffortName")); } Summary summary = new Summary(UtilProperties.getMessage("WorkEffortUiLabels", "WorkEffortEventReminder", Locale.getDefault())); Delegator delegator = workEffort.getDelegator(); String workEffortId = workEffort.getString("workEffortId"); List<GenericValue> reminderList = EntityQuery.use(delegator).from("WorkEffortEventReminder").where("workEffortId", workEffort.get("workEffortId")).queryList(); for (GenericValue reminder : reminderList) { String reminderId = workEffortId + "-" + reminder.getString("sequenceId"); VAlarm alarm = null; PropertyList alarmProps = null; boolean newAlarm = true; Iterator<VAlarm> i = UtilGenerics.cast(alarms.iterator()); while (i.hasNext()) { alarm = i.next(); Property xProperty = alarm.getProperty(reminderXPropName); if (xProperty != null && reminderId.equals(xProperty.getValue())) { newAlarm = false; alarmProps = alarm.getProperties(); // TODO: Write update code. For now, just re-create alarmProps.clear(); break; } } if (newAlarm) { alarm = createAlarm(reminder); alarms.add(alarm); alarmProps = alarm.getProperties(); alarmProps.add(new XProperty(reminderXPropName, reminderId)); } GenericValue contactMech = reminder.getRelatedOne("ContactMech", false); if (contactMech != null && "EMAIL_ADDRESS".equals(contactMech.get("contactMechTypeId"))) { try { alarmProps.add(new Attendee(contactMech.getString("infoString"))); alarmProps.add(Action.EMAIL); alarmProps.add(summary); alarmProps.add(description); } catch (URISyntaxException e) { alarmProps.add(Action.DISPLAY); alarmProps.add(new Description("Error encountered while creating iCalendar: " + e)); } } else { alarmProps.add(Action.DISPLAY); alarmProps.add(description); } if (Debug.verboseOn()) { try { alarm.validate(true); Debug.logVerbose("iCalendar alarm passes validation", module); } catch (ValidationException e) { Debug.logVerbose("iCalendar alarm fails validation: " + e, module); } } } } /** Returns a calendar derived from a Work Effort calendar publish point. * @param workEffortId ID of a work effort with <code>workEffortTypeId</code> equal to * <code>PUBLISH_PROPS</code>. * @param context The conversion context * @return An iCalendar as a <code>String</code>, or <code>null</code> * if <code>workEffortId</code> is invalid. * @throws GenericEntityException */ public static ResponseProperties getICalendar(String workEffortId, Map<String, Object> context) throws GenericEntityException { Delegator delegator = (Delegator) context.get("delegator"); GenericValue publishProperties = EntityQuery.use(delegator).from("WorkEffort").where("workEffortId", workEffortId).queryOne(); if (!isCalendarPublished(publishProperties)) { Debug.logInfo("WorkEffort calendar is not published: " + workEffortId, module); return ICalWorker.createNotFoundResponse(null); } if (!"WES_PUBLIC".equals(publishProperties.get("scopeEnumId"))) { if (context.get("userLogin") == null) { return ICalWorker.createNotAuthorizedResponse(null); } if (!hasPermission(workEffortId, "VIEW", context)) { return ICalWorker.createForbiddenResponse(null); } } Calendar calendar = makeCalendar(publishProperties, context); ComponentList components = calendar.getComponents(); List<GenericValue> workEfforts = getRelatedWorkEfforts(publishProperties, context); if (workEfforts != null) { for (GenericValue workEffort : workEfforts) { ResponseProperties responseProps = toCalendarComponent(components, workEffort, context); if (responseProps != null) { return responseProps; } } } if (Debug.verboseOn()) { try { calendar.validate(true); Debug.logVerbose("iCalendar passes validation", module); } catch (ValidationException e) { Debug.logVerbose("iCalendar fails validation: " + e, module); } } return ICalWorker.createOkResponse(calendar.toString()); } protected static void getPartyUrl(Property property, GenericValue partyAssign, Map<String, Object> context) { Map<String, ? extends Object> serviceMap = UtilMisc.toMap("partyId", partyAssign.get("partyId")); Map<String, Object> resultMap = invokeService("getPartyICalUrl", serviceMap, context); String iCalUrl = (String) resultMap.get("iCalUrl"); if (iCalUrl != null) { if (!iCalUrl.contains(":") && iCalUrl.contains("@")) { iCalUrl = "MAILTO:".concat(iCalUrl); } try { property.setValue(iCalUrl); } catch (Exception e) { Debug.logError(e, "Error while setting party URI: ", module); } } } protected static List<GenericValue> getRelatedWorkEfforts(GenericValue workEffort, Map<String, Object> context) throws GenericEntityException { Map<String, ? extends Object> serviceMap = UtilMisc.toMap("workEffortId", workEffort.getString("workEffortId")); Map<String, Object> resultMap = invokeService("getICalWorkEfforts", serviceMap, context); List<GenericValue> workEfforts = UtilGenerics.checkList(resultMap.get("workEfforts"), GenericValue.class); if (workEfforts != null) { return WorkEffortWorker.removeDuplicateWorkEfforts(workEfforts); } return null; } protected static boolean hasPermission(String workEffortId, String action, Map<String, Object> context) { if (context.get("userLogin") == null) { return false; } Map<String, ? extends Object> serviceMap = UtilMisc.toMap("workEffortId", workEffortId, "mainAction", action); Map<String, Object> serviceResult = invokeService("workEffortICalendarPermission", serviceMap, context); Boolean hasPermission = (Boolean) serviceResult.get("hasPermission"); if (hasPermission != null) { return hasPermission.booleanValue(); } else { return false; } } protected static Map<String, Object> invokeService(String serviceName, Map<String, ? extends Object> serviceMap, Map<String, Object> context) { LocalDispatcher dispatcher = (LocalDispatcher) context.get("dispatcher"); Locale locale = (Locale) context.get("locale"); Map<String, Object> localMap = new HashMap<String, Object>(); try { ModelService modelService = null; modelService = dispatcher.getDispatchContext().getModelService(serviceName); for (ModelParam modelParam: modelService.getInModelParamList()) { if (serviceMap.containsKey(modelParam.name)) { Object value = serviceMap.get(modelParam.name); if (UtilValidate.isNotEmpty(modelParam.type)) { value = ObjectType.simpleTypeConvert(value, modelParam.type, null, null, null, true); } localMap.put(modelParam.name, value); } } } catch (Exception e) { String errMsg = UtilProperties.getMessage("WorkEffortUiLabels", "WorkeffortErrorWhileCreatingServiceMapForService", UtilMisc.toMap("serviceName", serviceName), locale); Debug.logError(e, errMsg, module); return ServiceUtil.returnError(errMsg + e); } if (context.get("userLogin") != null) { localMap.put("userLogin", context.get("userLogin")); } localMap.put("locale", context.get("locale")); try { return dispatcher.runSync(serviceName, localMap); } catch (GenericServiceException e) { String errMsg = UtilProperties.getMessage("WorkEffortUiLabels", "WorkeffortErrorWhileInvokingService", UtilMisc.toMap("serviceName", serviceName), locale); Debug.logError(e, errMsg, module); return ServiceUtil.returnError(errMsg + e); } } protected static boolean isCalendarPublished(GenericValue publishProperties) { if (publishProperties == null || !"PUBLISH_PROPS".equals(publishProperties.get("workEffortTypeId"))) { return false; } DateRange range = new DateRange(publishProperties.getTimestamp("actualStartDate"), publishProperties.getTimestamp("actualCompletionDate")); return range.includesDate(new Date()); } protected static void loadPartyAssignment(Property property, GenericValue partyAssign, Map<String, Object> context) { getPartyUrl(property, partyAssign, context); if (UtilValidate.isEmpty(property.getValue())) { try { // RFC 2445 4.8.4.1 and 4.8.4.3 Value must be a URL property.setValue("MAILTO:ofbiz-test@example.com"); } catch (Exception e) { Debug.logError(e, "Error while setting Property value: ", module); } } ParameterList parameterList = property.getParameters(); if (partyAssign != null) { replaceParameter(parameterList, toXParameter(partyIdXParamName, partyAssign.getString("partyId"))); replaceParameter(parameterList, new Cn(makePartyName(partyAssign))); replaceParameter(parameterList, toParticipationStatus(partyAssign.getString("assignmentStatusId"))); } } protected static void loadRelatedParties(List<GenericValue> relatedParties, PropertyList componentProps, Map<String, Object> context) { PropertyList attendees = componentProps.getProperties("ATTENDEE"); for (GenericValue partyValue : relatedParties) { if ("CAL_ORGANIZER~CAL_OWNER".contains(partyValue.getString("roleTypeId"))) { // RFC 2445 4.6.1, 4.6.2, and 4.6.3 ORGANIZER can appear only once replaceProperty(componentProps, createOrganizer(partyValue, context)); } else { String partyId = partyValue.getString("partyId"); boolean newAttendee = true; Attendee attendee = null; Iterator<Attendee> i = UtilGenerics.cast(attendees.iterator()); while (i.hasNext()) { attendee = i.next(); Parameter xParameter = attendee.getParameter(partyIdXParamName); if (xParameter != null && partyId.equals(xParameter.getValue())) { loadPartyAssignment(attendee, partyValue, context); newAttendee = false; break; } } if (newAttendee) { attendee = createAttendee(partyValue, context); componentProps.add(attendee); } } } } protected static void loadWorkEffort(PropertyList componentProps, GenericValue workEffort) { replaceProperty(componentProps, new DtStamp()); // iCalendar object created date/time replaceProperty(componentProps, toClazz(workEffort.getString("scopeEnumId"))); replaceProperty(componentProps, toCreated(workEffort.getTimestamp("createdDate"))); replaceProperty(componentProps, toDescription(workEffort.getString("description"))); replaceProperty(componentProps, toDtStart(workEffort.getTimestamp("estimatedStartDate"))); replaceProperty(componentProps, toLastModified(workEffort.getTimestamp("lastModifiedDate"))); replaceProperty(componentProps, toPriority(workEffort.getLong("priority"))); replaceProperty(componentProps, toLocation(workEffort.getString("locationDesc"))); replaceProperty(componentProps, toStatus(workEffort.getString("currentStatusId"))); replaceProperty(componentProps, toSummary(workEffort.getString("workEffortName"))); Property uid = componentProps.getProperty(Uid.UID); if (uid == null) { // Don't overwrite UIDs created by calendar clients replaceProperty(componentProps, toUid(workEffort.getString("workEffortId"))); } replaceProperty(componentProps, toXProperty(workEffortIdXPropName, workEffort.getString("workEffortId"))); } protected static Calendar makeCalendar(GenericValue workEffort, Map<String, Object> context) throws GenericEntityException { String iCalData = null; GenericValue iCalValue = workEffort.getRelatedOne("WorkEffortIcalData", false); if (iCalValue != null) { iCalData = iCalValue.getString("icalData"); } boolean newCalendar = true; Calendar calendar = null; if (iCalData == null) { Debug.logVerbose("iCalendar Data not found, creating new Calendar", module); calendar = new Calendar(); } else { Debug.logVerbose("iCalendar Data found, using saved Calendar", module); StringReader reader = new StringReader(iCalData); CalendarBuilder builder = new CalendarBuilder(); try { calendar = builder.build(reader); newCalendar = false; } catch (Exception e) { Debug.logError(e, "Error while parsing saved iCalendar, creating new iCalendar: ", module); calendar = new Calendar(); } } PropertyList propList = calendar.getProperties(); replaceProperty(propList, prodId); replaceProperty(propList, new XProperty(workEffortIdXPropName, workEffort.getString("workEffortId"))); if (newCalendar) { propList.add(Version.VERSION_2_0); propList.add(CalScale.GREGORIAN); // TODO: Get time zone from publish properties value java.util.TimeZone tz = java.util.TimeZone.getDefault(); TimeZoneRegistry registry = TimeZoneRegistryFactory.getInstance().createRegistry(); net.fortuna.ical4j.model.TimeZone timezone = registry.getTimeZone(tz.getID()); calendar.getComponents().add(timezone.getVTimeZone()); } return calendar; } protected static String makePartyName(GenericValue partyAssign) { String partyName = partyAssign.getString("groupName"); if (UtilValidate.isEmpty(partyName)) { partyName = partyAssign.getString("firstName") + " " + partyAssign.getString("lastName"); } return partyName; } protected static void replaceParameter(ParameterList parameterList, Parameter parameter) { if (parameter == null) { return; } Parameter existingParam = parameterList.getParameter(parameter.getName()); if (existingParam != null) { parameterList.remove(existingParam); } parameterList.add(parameter); } protected static void replaceProperty(PropertyList propertyList, Property property) { if (property == null) { return; } Property existingProp = propertyList.getProperty(property.getName()); if (existingProp != null) { propertyList.remove(existingProp); } propertyList.add(property); } protected static void setMapElement(Map<String, Object> map, String key, Object value) { if (map == null || key == null || value == null) { return; } map.put(key, value); } protected static void setPartyIdFromUrl(Property property, Map<String, Object> context) { Map<String, ? extends Object> serviceMap = UtilMisc.toMap("address", property.getValue(), "caseInsensitive", "Y"); Map<String, Object> resultMap = invokeService("findPartyFromEmailAddress", serviceMap, context); String partyId = (String) resultMap.get("partyId"); if (partyId != null) { replaceParameter(property.getParameters(), toXParameter(partyIdXParamName, partyId)); } } protected static void setWorkEffortServiceMap(Component component, Map<String, Object> serviceMap) { PropertyList propertyList = component.getProperties(); setMapElement(serviceMap, "scopeEnumId", fromClazz(propertyList)); setMapElement(serviceMap, "description", fromDescription(propertyList)); setMapElement(serviceMap, "locationDesc", fromLocation(propertyList)); setMapElement(serviceMap, "priority", fromPriority(propertyList)); setMapElement(serviceMap, "currentStatusId", fromStatus(propertyList)); setMapElement(serviceMap, "workEffortName", fromSummary(propertyList)); setMapElement(serviceMap, "universalId", fromUid(propertyList)); // Set some fields to null so calendar clients can revert changes serviceMap.put("estimatedStartDate", null); serviceMap.put("estimatedCompletionDate", null); serviceMap.put("estimatedMilliSeconds", null); serviceMap.put("lastModifiedDate", null); serviceMap.put("actualCompletionDate", null); serviceMap.put("percentComplete", null); setMapElement(serviceMap, "estimatedStartDate", fromDtStart(propertyList)); setMapElement(serviceMap, "estimatedMilliSeconds", fromDuration(propertyList)); setMapElement(serviceMap, "lastModifiedDate", fromLastModified(propertyList)); if ("VTODO".equals(component.getName())) { setMapElement(serviceMap, "actualCompletionDate", fromCompleted(propertyList)); setMapElement(serviceMap, "percentComplete", fromPercentComplete(propertyList)); } else { setMapElement(serviceMap, "estimatedCompletionDate", fromDtEnd(propertyList)); } } /** Update work efforts from an incoming iCalendar request. * @param is * @param context * @throws IOException * @throws ParserException * @throws GenericEntityException * @throws GenericServiceException */ public static ResponseProperties storeCalendar(InputStream is, Map<String, Object> context) throws IOException, ParserException, GenericEntityException, GenericServiceException { CalendarBuilder builder = new CalendarBuilder(); Calendar calendar = null; try { calendar = builder.build(is); } finally { if (is != null) { is.close(); } } if (Debug.verboseOn()) { Debug.logVerbose("Processing calendar:\r\n" + calendar, module); } String workEffortId = fromXProperty(calendar.getProperties(), workEffortIdXPropName); if (workEffortId == null) { workEffortId = (String) context.get("workEffortId"); } if (!workEffortId.equals(context.get("workEffortId"))) { Debug.logWarning("Spoof attempt: received calendar workEffortId " + workEffortId + " on URL workEffortId " + context.get("workEffortId"), module); return ICalWorker.createForbiddenResponse(null); } Delegator delegator = (Delegator) context.get("delegator"); GenericValue publishProperties = EntityQuery.use(delegator).from("WorkEffort").where("workEffortId", workEffortId).queryOne(); if (!isCalendarPublished(publishProperties)) { Debug.logInfo("WorkEffort calendar is not published: " + workEffortId, module); return ICalWorker.createNotFoundResponse(null); } if (context.get("userLogin") == null) { return ICalWorker.createNotAuthorizedResponse(null); } if (!hasPermission(workEffortId, "UPDATE", context)) { return ICalWorker.createForbiddenResponse(null); } boolean hasCreatePermission = hasPermission(workEffortId, "CREATE", context); List<GenericValue> workEfforts = getRelatedWorkEfforts(publishProperties, context); Set<String> validWorkEfforts = new HashSet<String>(); if (UtilValidate.isNotEmpty(workEfforts)) { // Security issue: make sure only related work efforts get updated for (GenericValue workEffort : workEfforts) { validWorkEfforts.add(workEffort.getString("workEffortId")); } } List<Component> components = UtilGenerics.checkList(calendar.getComponents(), Component.class); ResponseProperties responseProps = null; for (Component component : components) { if (Component.VEVENT.equals(component.getName()) || Component.VTODO.equals(component.getName())) { workEffortId = fromXProperty(component.getProperties(), workEffortIdXPropName); if (workEffortId == null) { Property uid = component.getProperty(Uid.UID); if (uid != null) { GenericValue workEffort = EntityQuery.use(delegator).from("WorkEffort").where("universalId", uid.getValue()).queryFirst(); if (workEffort != null) { workEffortId = workEffort.getString("workEffortId"); } } } if (workEffortId != null) { if (validWorkEfforts.contains(workEffortId)) { replaceProperty(component.getProperties(), toXProperty(workEffortIdXPropName, workEffortId)); responseProps = storeWorkEffort(component, context); } else { Debug.logWarning("Spoof attempt: unrelated workEffortId " + workEffortId + " on URL workEffortId " + context.get("workEffortId"), module); responseProps = ICalWorker.createForbiddenResponse(null); } } else if (hasCreatePermission) { responseProps = createWorkEffort(component, context); } if (responseProps != null) { return responseProps; } } } Map<String, ? extends Object> serviceMap = UtilMisc.toMap("workEffortId", context.get("workEffortId"), "icalData", calendar.toString()); GenericValue iCalData = publishProperties.getRelatedOne("WorkEffortIcalData", false); Map<String, Object> serviceResult = null; if (iCalData == null) { serviceResult = invokeService("createWorkEffortICalData", serviceMap, context); } else { serviceResult = invokeService("updateWorkEffortICalData", serviceMap, context); } if (ServiceUtil.isError(serviceResult)) { return ICalWorker.createPartialContentResponse(ServiceUtil.getErrorMessage(serviceResult)); } return ICalWorker.createOkResponse(null); } protected static ResponseProperties storePartyAssignments(String workEffortId, Component component, Map<String, Object> context) { ResponseProperties responseProps = null; Map<String, Object> serviceMap = new HashMap<String, Object>(); List<Property> partyList = new LinkedList<Property>(); partyList.addAll(UtilGenerics.checkList(component.getProperties("ATTENDEE"), Property.class)); partyList.addAll(UtilGenerics.checkList(component.getProperties("CONTACT"), Property.class)); partyList.addAll(UtilGenerics.checkList(component.getProperties("ORGANIZER"), Property.class)); for (Property property : partyList) { String partyId = fromXParameter(property.getParameters(), partyIdXParamName); if (partyId == null) { serviceMap.clear(); String address = property.getValue(); if (address.toUpperCase().startsWith("MAILTO:")) { address = address.substring(7); } serviceMap.put("address", address); Map<String, Object> result = invokeService("findPartyFromEmailAddress", serviceMap, context); partyId = (String) result.get("partyId"); if (partyId == null) { continue; } replaceParameter(property.getParameters(), toXParameter(partyIdXParamName, partyId)); } serviceMap.clear(); serviceMap.put("workEffortId", workEffortId); serviceMap.put("partyId", partyId); serviceMap.put("roleTypeId", fromRoleMap.get(property.getName())); Delegator delegator = (Delegator) context.get("delegator"); List<GenericValue> assignments = null; try { assignments = EntityQuery.use(delegator).from("WorkEffortPartyAssignment").where(serviceMap).filterByDate().queryList(); if (assignments.size() == 0) { serviceMap.put("statusId", "PRTYASGN_OFFERED"); serviceMap.put("fromDate", new Timestamp(System.currentTimeMillis())); invokeService("assignPartyToWorkEffort", serviceMap, context); } } catch (GenericEntityException e) { responseProps = ICalWorker.createPartialContentResponse(e.getMessage()); break; } } return responseProps; } protected static ResponseProperties storeWorkEffort(Component component, Map<String, Object> context) throws GenericEntityException, GenericServiceException { PropertyList propertyList = component.getProperties(); String workEffortId = fromXProperty(propertyList, workEffortIdXPropName); Delegator delegator = (Delegator) context.get("delegator"); GenericValue workEffort = EntityQuery.use(delegator).from("WorkEffort").where("workEffortId", workEffortId).queryOne(); if (workEffort == null) { return ICalWorker.createNotFoundResponse(null); } if (!hasPermission(workEffortId, "UPDATE", context)) { return null; } Map<String, Object> serviceMap = new HashMap<String, Object>(); serviceMap.put("workEffortId", workEffortId); setWorkEffortServiceMap(component, serviceMap); invokeService("updateWorkEffort", serviceMap, context); return storePartyAssignments(workEffortId, component, context); } protected static ResponseProperties toCalendarComponent(ComponentList components, GenericValue workEffort, Map<String, Object> context) throws GenericEntityException { Delegator delegator = workEffort.getDelegator(); String workEffortId = workEffort.getString("workEffortId"); String workEffortUid = workEffort.getString("universalId"); String workEffortTypeId = workEffort.getString("workEffortTypeId"); GenericValue typeValue = EntityQuery.use(delegator).from("WorkEffortType").where("workEffortTypeId", workEffortTypeId).cache().queryOne(); boolean isTask = false; boolean newComponent = true; ComponentList resultList = null; ComponentList alarms = null; Component result = null; if ("TASK".equals(workEffortTypeId) || (typeValue != null && "TASK".equals(typeValue.get("parentTypeId")))) { isTask = true; resultList = components.getComponents("VTODO"); } else if ("EVENT".equals(workEffortTypeId) || (typeValue != null && "EVENT".equals(typeValue.get("parentTypeId")))) { resultList = components.getComponents("VEVENT"); } else { return null; } Iterator<Component> i = UtilGenerics.cast(resultList.iterator()); while (i.hasNext()) { result = i.next(); Property xProperty = result.getProperty(workEffortIdXPropName); if (xProperty != null && workEffortId.equals(xProperty.getValue())) { newComponent = false; break; } Property uid = result.getProperty(Uid.UID); if (uid != null && uid.getValue().equals(workEffortUid)) { newComponent = false; break; } } if (isTask) { VToDo toDo = null; if (newComponent) { toDo = new VToDo(); result = toDo; } else { toDo = (VToDo) result; } alarms = toDo.getAlarms(); } else { VEvent event = null; if (newComponent) { event = new VEvent(); result = event; } else { event = (VEvent) result; } alarms = event.getAlarms(); } if (newComponent) { components.add(result); } PropertyList componentProps = result.getProperties(); loadWorkEffort(componentProps, workEffort); if (isTask) { replaceProperty(componentProps, toCompleted(workEffort.getTimestamp("actualCompletionDate"))); replaceProperty(componentProps, toPercentComplete(workEffort.getLong("percentComplete"))); } else { replaceProperty(componentProps, toDtEnd(workEffort.getTimestamp("estimatedCompletionDate"))); } if (workEffort.get("estimatedCompletionDate") == null) { replaceProperty(componentProps, toDuration(workEffort.getDouble("estimatedMilliSeconds"))); } List<GenericValue> relatedParties = EntityQuery.use(delegator).from("WorkEffortPartyAssignView").where("workEffortId", workEffortId).cache(true).filterByDate().queryList(); if (relatedParties.size() > 0) { loadRelatedParties(relatedParties, componentProps, context); } if (newComponent) { if (UtilValidate.isNotEmpty(workEffort.getString("tempExprId"))) { TemporalExpression tempExpr = TemporalExpressionWorker.getTemporalExpression(delegator, workEffort.getString("tempExprId")); if (tempExpr != null) { try { ICalRecurConverter.convert(tempExpr, componentProps); } catch (Exception e) { replaceProperty(componentProps, new Description("Error while converting recurrence: " + e)); } } } getAlarms(workEffort, alarms); } if (Debug.verboseOn()) { try { result.validate(true); Debug.logVerbose("iCalendar component passes validation", module); } catch (ValidationException e) { Debug.logVerbose(e, "iCalendar component fails validation: ", module); } } return null; } protected static Clazz toClazz(String javaObj) { if (javaObj == null) { return null; } return new Clazz(javaObj.replace("WES_", "")); } protected static Completed toCompleted(Timestamp javaObj) { if (javaObj == null) { return null; } return new Completed(new DateTime(javaObj)); } protected static Created toCreated(Timestamp javaObj) { if (javaObj == null) { return null; } return new Created(new DateTime(javaObj)); } protected static Description toDescription(String javaObj) { if (javaObj == null) { return null; } return new Description(javaObj); } protected static DtEnd toDtEnd(Timestamp javaObj) { if (javaObj == null) { return null; } return new DtEnd(new DateTime(javaObj)); } protected static DtStart toDtStart(Timestamp javaObj) { if (javaObj == null) { return null; } return new DtStart(new DateTime(javaObj)); } protected static Duration toDuration(Double javaObj) { if (javaObj == null) { return null; } TimeDuration duration = TimeDuration.fromNumber(javaObj); return new Duration(new Dur(duration.days(), duration.hours(), duration.minutes(), duration.seconds())); } protected static LastModified toLastModified(Timestamp javaObj) { if (javaObj == null) { return null; } return new LastModified(new DateTime(javaObj)); } protected static Location toLocation(String javaObj) { if (javaObj == null) { return null; } return new Location(javaObj); } protected static PartStat toParticipationStatus(String statusId) { if (statusId == null) { return null; } return toPartStatusMap.get(statusId); } protected static PercentComplete toPercentComplete(Long javaObj) { if (javaObj == null) { return null; } return new PercentComplete(javaObj.intValue()); } protected static Priority toPriority(Long javaObj) { if (javaObj == null) { return null; } return new Priority(javaObj.intValue()); } protected static Status toStatus(String javaObj) { if (javaObj == null) { return null; } return toStatusMap.get(javaObj); } protected static Summary toSummary(String javaObj) { if (javaObj == null) { return null; } return new Summary(javaObj); } protected static Uid toUid(String javaObj) { if (javaObj == null) { return null; } return new Uid(uidPrefix.concat(javaObj)); } protected static XParameter toXParameter(String name, String value) { if (name == null || value == null) { return null; } return new XParameter(name, value); } protected static XProperty toXProperty(String name, String value) { if (name == null || value == null) { return null; } return new XProperty(name, value); } }