Author: adrianc
Date: Fri Jun 12 17:03:42 2009 New Revision: 784195 URL: http://svn.apache.org/viewvc?rev=784195&view=rev Log: Improved iCalendar integration: 1. Work effort tasks are now converted to VTODO. 2. Event reminders can be a popup alarm 3. Fixed invalid iCalendar creation Tested with Mozilla Sunbird. Sunbird has some annoying bugs when using read-only calendars - which they are aware of but haven't fixed. Someday I'll make the iCalendar read/write. This commit includes a migration service for anyone who was using this feature previously. Modified: ofbiz/trunk/applications/workeffort/config/WorkEffortUiLabels.xml ofbiz/trunk/applications/workeffort/entitydef/entitymodel.xml ofbiz/trunk/applications/workeffort/servicedef/services.xml ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalendarWorker.java ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/WorkEffortServices.java ofbiz/trunk/applications/workeffort/widget/WorkEffortForms.xml ofbiz/trunk/framework/common/webcommon/includes/timeDuration.ftl Modified: ofbiz/trunk/applications/workeffort/config/WorkEffortUiLabels.xml URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/workeffort/config/WorkEffortUiLabels.xml?rev=784195&r1=784194&r2=784195&view=diff ============================================================================== --- ofbiz/trunk/applications/workeffort/config/WorkEffortUiLabels.xml (original) +++ ofbiz/trunk/applications/workeffort/config/WorkEffortUiLabels.xml Fri Jun 12 17:03:42 2009 @@ -288,16 +288,16 @@ <value xml:lang="it">Classifica</value> <value xml:lang="th">à¸à¸²à¸£à¸à¸£à¸°à¹à¸¡à¸´à¸</value> </property> - <property key="FormFieldTitle_recurrenceOffset"> - <value xml:lang="en">Recurrence Offset</value> - <value xml:lang="fr">Récurrence de décalage</value> - <value xml:lang="it">Intervallo ricorrenza</value> - </property> <property key="FormFieldTitle_reminderDateTime"> <value xml:lang="en">Reminder Date Time</value> <value xml:lang="fr">Date heure de rappel</value> <value xml:lang="it">Data ora reminder</value> </property> + <property key="FormFieldTitle_reminderOffset"> + <value xml:lang="en">Reminder Offset</value> + <value xml:lang="fr">Récurrence de décalage</value> + <value xml:lang="it">Intervallo ricorrenza</value> + </property> <property key="FormFieldTitle_repeatInterval"> <value xml:lang="en">Repeat Interval</value> <value xml:lang="fr">Répéter intervalle</value> Modified: ofbiz/trunk/applications/workeffort/entitydef/entitymodel.xml URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/workeffort/entitydef/entitymodel.xml?rev=784195&r1=784194&r2=784195&view=diff ============================================================================== --- ofbiz/trunk/applications/workeffort/entitydef/entitymodel.xml (original) +++ ofbiz/trunk/applications/workeffort/entitydef/entitymodel.xml Fri Jun 12 17:03:42 2009 @@ -468,17 +468,16 @@ package-name="org.ofbiz.workeffort.workeffort" title="Work Effort Event Reminder Entity"> <field name="workEffortId" type="id-ne"></field> - <field name="contactMechId" type="id-ne"></field> <field name="sequenceId" type="id-ne"></field> + <field name="contactMechId" type="id"></field> <field name="reminderDateTime" type="date-time"></field> <field name="repeatCount" type="numeric"></field> <field name="repeatInterval" type="numeric"> <description>The millisecond interval between reminder repeats</description> </field> <field name="currentCount" type="numeric"></field> - <field name="recurrenceOffset" type="numeric"> - <description>If the work effort is recurring, the millisecond offset from - the recurring event that will be used to calculate the reminder date/time</description> + <field name="reminderOffset" type="numeric"> + <description>The millisecond offset from the event to activate a reminder</description> </field> <field name="localeId" type="id"></field> <field name="timeZoneId" type="id-long"></field> Modified: ofbiz/trunk/applications/workeffort/servicedef/services.xml URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/workeffort/servicedef/services.xml?rev=784195&r1=784194&r2=784195&view=diff ============================================================================== --- ofbiz/trunk/applications/workeffort/servicedef/services.xml (original) +++ ofbiz/trunk/applications/workeffort/servicedef/services.xml Fri Jun 12 17:03:42 2009 @@ -685,6 +685,10 @@ location="org.ofbiz.workeffort.workeffort.WorkEffortServices" invoke="processWorkEffortEventReminders" auth="true"> <description>Process work effort event reminders. This service is run by the job scheduler.</description> </service> + <service name="migrateWorkEffortEventReminders" engine="java" + location="org.ofbiz.workeffort.workeffort.WorkEffortServices" invoke="migrateWorkEffortEventReminders" auth="true"> + <description>Migrate work effort event reminders. Run this service to update work effort reminders.</description> + </service> <!-- WorkEffort and Survey Services --> <service name="createWorkEffortSurveyAppl" engine="simple" default-entity-name="WorkEffortSurveyAppl" Modified: ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalendarWorker.java URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalendarWorker.java?rev=784195&r1=784194&r2=784195&view=diff ============================================================================== --- ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalendarWorker.java (original) +++ ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalendarWorker.java Fri Jun 12 17:03:42 2009 @@ -19,9 +19,11 @@ package org.ofbiz.workeffort.workeffort; +import java.net.URISyntaxException; +import java.sql.Timestamp; import java.util.List; +import java.util.Locale; import java.util.Map; -import java.util.TimeZone; import javolution.util.FastList; @@ -34,40 +36,69 @@ import org.ofbiz.base.util.Debug; import org.ofbiz.base.util.TimeDuration; import org.ofbiz.base.util.UtilMisc; +import org.ofbiz.base.util.UtilProperties; import org.ofbiz.base.util.UtilValidate; import org.ofbiz.entity.GenericDelegator; import org.ofbiz.entity.GenericEntityException; import org.ofbiz.entity.GenericValue; import org.ofbiz.entity.condition.EntityCondition; -import org.ofbiz.entity.condition.EntityConditionList; import org.ofbiz.entity.condition.EntityExpr; import org.ofbiz.entity.condition.EntityOperator; import org.ofbiz.entity.util.EntityUtil; import org.ofbiz.service.calendar.TemporalExpression; import org.ofbiz.service.calendar.TemporalExpressionWorker; -/** iCalendar worker class. */ +/** iCalendar worker class. This class uses the <a href="http://ical4j.sourceforge.net/index.html"> + * iCal4J</a> library. */ public class ICalendarWorker { public static final String module = ICalendarWorker.class.getName(); - protected static ProdId prodId = new ProdId("-//Apache Open For Business//Work Effort Calendar//EN"); - protected static Map<String, Status> statusMap = UtilMisc.toMap("CAL_TENTATIVE", Status.VEVENT_TENTATIVE, + + protected static final ProdId prodId = new ProdId("-//Apache Open For Business//Work Effort Calendar//EN"); + protected static final Map<String, Status> statusMap = UtilMisc.toMap("CAL_TENTATIVE", Status.VEVENT_TENTATIVE, "CAL_CONFIRMED", Status.VEVENT_CONFIRMED, "CAL_CANCELLED", Status.VEVENT_CANCELLED); - protected static String workEffortIdPropName = "X-ORG-OFBIZ-WORKEFFORT-ID"; + protected static final String uidPrefix = "org-apache-ofbiz-we-"; + /** Returns a calendar derived from a Work Effort calendar publish point. + * + * @param delegator + * @param workEffortId ID of a work effort with <code>workEffortTypeId</code> equal to + * <code>PUBLISH_PROPS</code>. + * @return A <code>net.fortuna.ical4j.model.Calendar</code> instance, or <code>null</code> + * if <code>workEffortId</code> is invalid. + * @throws GenericEntityException + */ public static net.fortuna.ical4j.model.Calendar getICalendar(GenericDelegator delegator, String workEffortId) throws GenericEntityException { - GenericValue calendarProperties = delegator.findByPrimaryKey("WorkEffort", UtilMisc.toMap("workEffortId", workEffortId)); - if (calendarProperties == null || !"PUBLISH_PROPS".equals(calendarProperties.get("workEffortTypeId"))) { + GenericValue publishProperties = delegator.findByPrimaryKey("WorkEffort", UtilMisc.toMap("workEffortId", workEffortId)); + if (publishProperties == null || !"PUBLISH_PROPS".equals(publishProperties.get("workEffortTypeId"))) { return null; } - net.fortuna.ical4j.model.Calendar calendar = makeCalendar(calendarProperties); + net.fortuna.ical4j.model.Calendar calendar = makeCalendar(publishProperties); ComponentList components = calendar.getComponents(); - List<GenericValue> workEfforts = getRelatedWorkEfforts(calendarProperties); + List<GenericValue> workEfforts = getRelatedWorkEfforts(publishProperties); for (GenericValue workEffort : workEfforts) { - components.add(makeEvent(workEffort)); + components.add(makeCalendarComponent(workEffort)); + } + if (Debug.verboseOn()) { + try { + calendar.validate(true); + Debug.logVerbose("iCalendar passes validation", module); + } catch (ValidationException e) { + Debug.logVerbose("iCalendar fails validation: " + e, module); + } } return calendar; } + /** Returns a <code>List</code> of work efforts related to a work effort calendar + * publish point.<p>The <code>List</code> includes:<ul><li>All public work efforts of all + * parties related to the publish point work effort</li><li>All public work efforts + * of all fixed assets related to the publish point work effort</li><li>All + * child work efforts of the publish point work effort</li></ul></p> + * + * @param workEffort + * @return A <code>List</code> of related work efforts + * @throws GenericEntityException + */ public static List<GenericValue> getRelatedWorkEfforts(GenericValue workEffort) throws GenericEntityException { GenericDelegator delegator = workEffort.getDelegator(); String workEffortId = workEffort.getString("workEffortId"); @@ -93,7 +124,15 @@ return WorkEffortWorker.removeDuplicateWorkEfforts(workEfforts); } - public static VEvent makeEvent(GenericValue workEffort) throws GenericEntityException { + /** Returns a <code>Component</code> instance based on a work effort. + * If the work effort is a task, then a <code>VToDo</code> is returned, + * otherwise a <code>VEvent</code> is returned. + * + * @param workEffort + * @return A <code>VToDo</code> or <code>VEvent</code> instance + * @throws GenericEntityException + */ + public static Component makeCalendarComponent(GenericValue workEffort) throws GenericEntityException { GenericDelegator delegator = workEffort.getDelegator(); String workEffortId = workEffort.getString("workEffortId"); PropertyList eventProps = new PropertyList(); @@ -104,7 +143,7 @@ if (workEffort.getTimestamp("lastModifiedDate") != null) { eventProps.add(new LastModified(new DateTime(workEffort.getTimestamp("lastModifiedDate")))); } - eventProps.add(new XProperty(workEffortIdPropName, workEffort.getString("workEffortId"))); + eventProps.add(new Uid(uidPrefix.concat(workEffortId))); eventProps.add(new Summary(workEffort.getString("workEffortName"))); Status eventStatus = statusMap.get(workEffort.getString("currentStatusId")); if (eventStatus != null) { @@ -126,9 +165,9 @@ // paramList.add(new XParameter(partyIdPropName, partyValue.getString("partyId"))); try { if ("CAL_ORGANIZER~CAL_OWNER".contains(partyValue.getString("roleTypeId"))) { - eventProps.add(new Organizer(paramList, "")); + eventProps.add(new Organizer("CN:".concat(partyName))); } else { - eventProps.add(new Attendee(paramList, "")); + eventProps.add(new Attendee("CN:".concat(partyName))); } } catch (Exception e) {} } @@ -152,20 +191,42 @@ if (workEffort.getString("description") != null) { eventProps.add(new Description(workEffort.getString("description"))); } - return new VEvent(eventProps); + ComponentList alarms = null; + Component result = null; + if ("TASK".equals(workEffort.get("workEffortTypeId"))) { + VToDo toDo = new VToDo(eventProps); + alarms = toDo.getAlarms(); + result = toDo; + } else { + VEvent event = new VEvent(eventProps); + alarms = event.getAlarms(); + result = event; + } + getAlarms(workEffort, alarms); + if (Debug.verboseOn()) { + try { + result.validate(true); + Debug.logVerbose("iCalendar component passes validation", module); + } catch (ValidationException e) { + Debug.logVerbose("iCalendar component fails validation: " + e, module); + } + } + return result; } + /** Returns a new <code>net.fortuna.ical4j.model.Calendar</code> instance, + * based on a work effort calendar publish point. + * + * @param workEffort + * @return + * @throws GenericEntityException + */ public static net.fortuna.ical4j.model.Calendar makeCalendar(GenericValue workEffort) throws GenericEntityException { net.fortuna.ical4j.model.Calendar calendar = new net.fortuna.ical4j.model.Calendar(); PropertyList propList = calendar.getProperties(); propList.add(prodId); propList.add(Version.VERSION_2_0); propList.add(CalScale.GREGORIAN); - if (workEffort.get("description") != null) { - propList.add(new Description(workEffort.getString("description"))); - } else { - propList.add(new Description(workEffort.getString("workEffortName"))); - } // TODO: Get time zone from publish properties value java.util.TimeZone tz = java.util.TimeZone.getDefault(); TimeZoneRegistry registry = TimeZoneRegistryFactory.getInstance().createRegistry(); @@ -173,4 +234,72 @@ calendar.getComponents().add(timezone.getVTimeZone()); return calendar; } + + /** Converts <code>WorkEffortEventReminder</code> entities to <code>VAlarm</code> + * instances, and adds them to a <code>ComponentList</code>. + * + * @param workEffort The work effort to get the event reminders for + * @param alarms The <code>ComponentList</code> that will contain the + * <code>VAlarm</code> instances + * @throws GenericEntityException + */ + public 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())); + GenericDelegator delegator = workEffort.getDelegator(); + List<GenericValue> reminderList = delegator.findList("WorkEffortEventReminder", EntityCondition.makeCondition("workEffortId", EntityOperator.EQUALS, workEffort.get("workEffortId")), null, null, null, false); + for (GenericValue reminder : reminderList) { + VAlarm alarm = createAlarm(reminder); + PropertyList alarmProps = alarm.getProperties(); + GenericValue contactMech = reminder.getRelatedOne("ContactMech"); + 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); + } + } + alarms.add(alarm); + } + } + + /** Converts a <code>WorkEffortEventReminder</code> entity to a + * <code>VAlarm</code> instance. + * + * @param workEffortEventReminder + * @return A <code>VAlarm</code> instance + * @throws GenericEntityException + */ + public static VAlarm createAlarm(GenericValue workEffortEventReminder) { + VAlarm alarm = null; + Timestamp reminderStamp = workEffortEventReminder.getTimestamp("reminderDateTime"); + if (reminderStamp != null) { + alarm = new VAlarm(new DateTime(reminderStamp)); + } else { + long reminderOffset = workEffortEventReminder.get("reminderOffset") == null ? 0 : workEffortEventReminder.getLong("reminderOffset").longValue(); + TimeDuration duration = TimeDuration.fromLong(reminderOffset); + alarm = new VAlarm(new Dur(duration.days(), duration.hours(), duration.minutes(), duration.seconds())); + } + return alarm; + } } Modified: ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/WorkEffortServices.java URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/WorkEffortServices.java?rev=784195&r1=784194&r2=784195&view=diff ============================================================================== --- ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/WorkEffortServices.java (original) +++ ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/WorkEffortServices.java Fri Jun 12 17:03:42 2009 @@ -49,6 +49,8 @@ import org.ofbiz.entity.condition.EntityExpr; import org.ofbiz.entity.condition.EntityJoinOperator; import org.ofbiz.entity.condition.EntityOperator; +import org.ofbiz.entity.model.ModelEntity; +import org.ofbiz.entity.model.ModelField; import org.ofbiz.entity.util.EntityUtil; import org.ofbiz.security.Security; import org.ofbiz.service.DispatchContext; @@ -747,6 +749,9 @@ return ServiceUtil.returnError("Error while retrieving work effort event reminders: " + e); } for (GenericValue reminder : eventReminders) { + if (UtilValidate.isEmpty(reminder.get("contactMechId"))) { + continue; + } int repeatCount = reminder.get("repeatCount") == null ? 0 : reminder.getLong("repeatCount").intValue(); int currentCount = reminder.get("currentCount") == null ? 0 : reminder.getLong("currentCount").intValue(); GenericValue workEffort = null; @@ -780,11 +785,11 @@ if (temporalExpression != null) { eventDateTime = temporalExpression.first(cal).getTime(); Date reminderDateTime = null; - long recurrenceOffset = reminder.get("recurrenceOffset") == null ? 0 : reminder.getLong("recurrenceOffset").longValue(); + long reminderOffset = reminder.get("reminderOffset") == null ? 0 : reminder.getLong("reminderOffset").longValue(); if (reminderStamp == null) { - if (recurrenceOffset != 0) { + if (reminderOffset != 0) { cal.setTime(eventDateTime); - TimeDuration duration = TimeDuration.fromLong(recurrenceOffset); + TimeDuration duration = TimeDuration.fromLong(reminderOffset); duration.addToCalendar(cal); reminderDateTime = cal.getTime(); } else { @@ -802,11 +807,11 @@ } else { cal.setTime(reminderDateTime); Date newReminderDateTime = null; - if (recurrenceOffset != 0) { - TimeDuration duration = TimeDuration.fromLong(-recurrenceOffset); + if (reminderOffset != 0) { + TimeDuration duration = TimeDuration.fromLong(-reminderOffset); duration.addToCalendar(cal); cal.setTime(temporalExpression.next(cal).getTime()); - duration = TimeDuration.fromLong(recurrenceOffset); + duration = TimeDuration.fromLong(reminderOffset); duration.addToCalendar(cal); newReminderDateTime = cal.getTime(); } else { @@ -876,4 +881,30 @@ // TODO: Other contact mechanism types Debug.logWarning("Invalid event reminder contact mech, workEffortId = " + reminder.get("workEffortId") + ", contactMechId = " + reminder.get("contactMechId"), module); } + + /** Migrate work effort event reminders. + * @param ctx + * @param context + * @return + */ + @SuppressWarnings("deprecation") + public static Map<String, Object> migrateWorkEffortEventReminders(DispatchContext ctx, Map<String, ? extends Object> context) { + GenericDelegator delegator = ctx.getDelegator(); + ModelEntity modelEntity = delegator.getModelEntity("WorkEffortEventReminder"); + if (modelEntity != null && modelEntity.getField("recurrenceOffset") != null) { + List<GenericValue> eventReminders = null; + try { + eventReminders = delegator.findAll("WorkEffortEventReminder"); + for (GenericValue reminder : eventReminders) { + if (UtilValidate.isNotEmpty(reminder.get("recurrenceOffset"))) { + reminder.set("reminderOffset", reminder.get("recurrenceOffset")); + reminder.store(); + } + } + } catch (GenericEntityException e) { + return ServiceUtil.returnError("Error while migrating work effort event reminders: " + e); + } + } + return ServiceUtil.returnSuccess(); + } } Modified: ofbiz/trunk/applications/workeffort/widget/WorkEffortForms.xml URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/workeffort/widget/WorkEffortForms.xml?rev=784195&r1=784194&r2=784195&view=diff ============================================================================== --- ofbiz/trunk/applications/workeffort/widget/WorkEffortForms.xml (original) +++ ofbiz/trunk/applications/workeffort/widget/WorkEffortForms.xml Fri Jun 12 17:03:42 2009 @@ -1309,7 +1309,7 @@ <field name="reminderDateTime"><date-time/></field> <field name="repeatCount"><text/></field> <field name="repeatInterval"><text/></field> - <field name="recurrenceOffset"><text/></field> + <field name="reminderOffset"><text/></field> <field name="submitButton" title="${uiLabelMap.CommonSave}" widget-style="smallSubmit"><submit button-type="button"/></field> <field name="deleteLink" title="${uiLabelMap.CommonEmptyHeader}" widget-style="buttontext"> <hyperlink also-hidden="false" description="${uiLabelMap.CommonDelete}" target="deleteWorkEffortEventReminder"> @@ -1325,11 +1325,11 @@ <field name="workEffortId"><hidden/></field> <field name="localeId"><hidden value="${locale}"/></field> <field name="timeZoneId"><hidden value="${timeZone}"/></field> - <field name="contactMechId" tooltip="${uiLabelMap.CommonRequired}" widget-style="required"><lookup target-form-name="LookupContactMech"/></field> + <field name="contactMechId"><lookup target-form-name="LookupContactMech"/></field> <field name="reminderDateTime"><date-time/></field> <field name="repeatCount"><text/></field> <field name="repeatInterval"><text/></field> - <field name="recurrenceOffset"><text/></field> + <field name="reminderOffset"><lookup target-form-name="LookupTimeDuration"/></field> <field name="submitButton" title="${uiLabelMap.CommonAdd}" widget-style="smallSubmit"><submit button-type="button"/></field> </form> Modified: ofbiz/trunk/framework/common/webcommon/includes/timeDuration.ftl URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/common/webcommon/includes/timeDuration.ftl?rev=784195&r1=784194&r2=784195&view=diff ============================================================================== --- ofbiz/trunk/framework/common/webcommon/includes/timeDuration.ftl (original) +++ ofbiz/trunk/framework/common/webcommon/includes/timeDuration.ftl Fri Jun 12 17:03:42 2009 @@ -44,7 +44,7 @@ </tr> <tr> <td class="label">${uiLabelMap.CommonWeek}</td> - <td><input type="text" name="weeks" maxlength="2"/></td> + <td><input type="text" name="weeks" size="4" maxlength="2"/></td> </tr> <tr> <td class="label">${uiLabelMap.CommonDay}</td> @@ -88,7 +88,7 @@ </tr> <tr> <td class="label">${uiLabelMap.CommonMilliSecond}</td> - <td><input type="text" name="millis" maxlength="4"/></td> + <td><input type="text" name="millis" size="4" maxlength="4"/></td> </tr> <tr> <td> </td> |
Free forum by Nabble | Edit this page |