svn commit: r787927 [1/2] - in /ofbiz/trunk/applications/workeffort: data/ entitydef/ script/org/ofbiz/workeffort/permission/ script/org/ofbiz/workeffort/workeffort/ servicedef/ src/org/ofbiz/workeffort/workeffort/ webapp/ical/WEB-INF/

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

svn commit: r787927 [1/2] - in /ofbiz/trunk/applications/workeffort: data/ entitydef/ script/org/ofbiz/workeffort/permission/ script/org/ofbiz/workeffort/workeffort/ servicedef/ src/org/ofbiz/workeffort/workeffort/ webapp/ical/WEB-INF/

adrianc
Author: adrianc
Date: Wed Jun 24 06:58:54 2009
New Revision: 787927

URL: http://svn.apache.org/viewvc?rev=787927&view=rev
Log:
Improved Work Effort iCalendar support:

1. Improved work effort field-to-iCalendar property mapping.
2. Work efforts can be updated from 3rd party calendar clients.
3. The publish point scope controls calendar access
4. Some iCalendar WebDAV servlet operations use services - so the iCalendar integration can be modified or enhanced.

TODO: I'm not an expert on SSL - the request handler needs more work to enforce SSL. Allow third party calendar programs to create new work efforts. Implement a subset of CalDAV. Authentication is done with URL parameters - that needs to be improved.

I replaced the work effort aatribute services I set up earlier. The attributes were too limited.

I will create a Wiki page soon to explain all of this.

Many thanks to David Jones for his advice, and to Hans Bakker for his calendar demo data.

Added:
    ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalConverter.java   (with props)
    ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalHandlerFactory.java   (with props)
Removed:
    ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalServlet.java
Modified:
    ofbiz/trunk/applications/workeffort/data/WorkEffortDemoData.xml
    ofbiz/trunk/applications/workeffort/entitydef/entitymodel.xml
    ofbiz/trunk/applications/workeffort/script/org/ofbiz/workeffort/permission/WorkEffortPermissionServices.xml
    ofbiz/trunk/applications/workeffort/script/org/ofbiz/workeffort/workeffort/WorkEffortSimpleServices.xml
    ofbiz/trunk/applications/workeffort/servicedef/services.xml
    ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalWorker.java
    ofbiz/trunk/applications/workeffort/webapp/ical/WEB-INF/web.xml

Modified: ofbiz/trunk/applications/workeffort/data/WorkEffortDemoData.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/workeffort/data/WorkEffortDemoData.xml?rev=787927&r1=787926&r2=787927&view=diff
==============================================================================
--- ofbiz/trunk/applications/workeffort/data/WorkEffortDemoData.xml (original)
+++ ofbiz/trunk/applications/workeffort/data/WorkEffortDemoData.xml Wed Jun 24 06:58:54 2009
@@ -47,7 +47,7 @@
     <WorkEffortAssoc workEffortIdFrom="CALENDAR_PUB_DEMO" workEffortIdTo="PrivateDemoEmployee1" workEffortAssocTypeId="WORK_EFF_DEPENDENCY" fromDate="2008-01-01 00:00:00.0"/>
     <WorkEffortPartyAssignment workEffortId="PrivateDemoEmployee1" partyId="DemoEmployee1" statusId="PRTYASGN_ASSIGNED" roleTypeId="CAL_OWNER" availabilityStatusId="WEPA_AV_BUSY" fromDate="2008-01-01 00:00:00.0"/>
     <!-- public event -->
-    <WorkEffort workEffortId="PublicEvent" workEffortTypeId="MEETING" currentStatusId="CAL_TENTATIVE" lastStatusUpdate="2008-01-01 00:00:00.0" scopeEnumId="WES_PUBLIC" workEffortName="The general company party june 17" description="General Party" estimatedStartDate="2009-06-17 19:00:00.0" estimatedCompletionDate="2009-06-17 23:00:00.0"/>
+    <WorkEffort workEffortId="PublicEvent" workEffortTypeId="MEETING" currentStatusId="CAL_TENTATIVE" lastStatusUpdate="2008-01-01 00:00:00.0" scopeEnumId="WES_PUBLIC" workEffortName="The general company party june 17" description="General Party" locationDesc="Tom's Banquet Hall" estimatedStartDate="2009-06-17 19:00:00.0" estimatedCompletionDate="2009-06-17 23:00:00.0"/>
     <WorkEffortAssoc workEffortIdFrom="CALENDAR_PUB_DEMO" workEffortIdTo="PublicEvent" workEffortAssocTypeId="WORK_EFF_DEPENDENCY" fromDate="2008-01-01 00:00:00.0"/>
     
 </entity-engine-xml>

Modified: ofbiz/trunk/applications/workeffort/entitydef/entitymodel.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/workeffort/entitydef/entitymodel.xml?rev=787927&r1=787926&r2=787927&view=diff
==============================================================================
--- ofbiz/trunk/applications/workeffort/entitydef/entitymodel.xml (original)
+++ ofbiz/trunk/applications/workeffort/entitydef/entitymodel.xml Wed Jun 24 06:58:54 2009
@@ -578,6 +578,18 @@
         <key-map field-name="parentTypeId" rel-field-name="workEffortGoodStdTypeId"/>
       </relation>
     </entity>
+    <entity entity-name="WorkEffortIcalData"
+            package-name="org.ofbiz.workeffort.workeffort"
+            title="Work Effort iCalendar Data">
+        <field name="workEffortId" type="id-ne"></field>
+        <field name="icalData" type="very-long">
+            <description>iCalender Data</description>
+        </field>
+        <prim-key field="workEffortId"/>
+        <relation type="one" fk-name="WKEFF_ICAL_DATA" rel-entity-name="WorkEffort">
+            <key-map field-name="workEffortId"/>
+        </relation>
+    </entity>
     <entity entity-name="WorkEffortInventoryAssign"
             package-name="org.ofbiz.workeffort.workeffort"
             title="Work Effort Inventory Assignment Entity">

Modified: ofbiz/trunk/applications/workeffort/script/org/ofbiz/workeffort/permission/WorkEffortPermissionServices.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/workeffort/script/org/ofbiz/workeffort/permission/WorkEffortPermissionServices.xml?rev=787927&r1=787926&r2=787927&view=diff
==============================================================================
--- ofbiz/trunk/applications/workeffort/script/org/ofbiz/workeffort/permission/WorkEffortPermissionServices.xml (original)
+++ ofbiz/trunk/applications/workeffort/script/org/ofbiz/workeffort/permission/WorkEffortPermissionServices.xml Wed Jun 24 06:58:54 2009
@@ -303,4 +303,80 @@
         </while>
     </simple-method>
 
+    <simple-method method-name="workEffortICalendarPermission" short-description="Check iCalendar Permission">
+        <!-- Note: the iCalendar servlet will call the permission service under two conditions - to grant
+             VIEW access to a calendar that isn't PUBLIC, or to grant UPDATE access to a work effort -->
+        <log level="verbose" message="workEffortICalendarPermission invoked for workEffortId ${parameters.workEffortId},
+            user login partyId = ${userLogin.partyId}"/>
+        <call-simple-method method-name="workEffortManagerPermission"/>
+        <if-compare field="hasPermission" value="true" operator="equals">
+            <set field="hasPermission" value="false" type="Boolean"/>
+            <set field="workEffortId" from-field="parameters.workEffortId"/>
+            <entity-one value-field="workEffort" entity-name="WorkEffort"/>
+            <if-not-empty field="workEffort">
+                <entity-condition list="partyAssignments" entity-name="WorkEffortPartyAssignment" filter-by-date="true">
+                    <condition-list combine="and">
+                        <condition-expr field-name="workEffortId" from-field="workEffortId"/>
+                        <condition-expr field-name="partyId" from-field="userLogin.partyId"/>
+                        <condition-list combine="or">
+                            <condition-expr field-name="roleTypeId" value="CAL_OWNER"/>
+                            <condition-expr field-name="roleTypeId" value="CAL_ORGANIZER"/>
+                            <condition-expr field-name="roleTypeId" value="CAL_DELEGATE"/>
+                        </condition-list>
+                    </condition-list>
+                </entity-condition>
+                <set field="isDelegate" value="false"/>
+                <set field="isOwner" value="false"/>
+                <set field="isOrganizer" value="false"/>
+                <iterate list="partyAssignments" entry="partyAssignment">
+                    <if-compare field="partyAssignment.roleTypeId" operator="equals" value="CAL_OWNER">
+                        <set field="isDelegate" value="true"/>
+                        <set field="isOwner" value="true"/>
+                        <set field="isOrganizer" value="true"/>
+                    <else>
+                        <if-compare field="partyAssignment.roleTypeId" operator="equals" value="CAL_ORGANIZER">
+                            <set field="isOrganizer" value="true"/>
+                            <else>
+                                <if-compare field="partyAssignment.roleTypeId" operator="equals" value="CAL_DELEGATE">
+                                    <set field="isDelegate" value="true"/>
+                                </if-compare>
+                            </else>
+                        </if-compare>
+                    </else>
+                    </if-compare>
+                </iterate>
+                <if-compare value="PUBLISH_PROPS" field="workEffort.workEffortTypeId" operator="equals">
+                    <log level="verbose" message="Checking publish properties permission, isOwner = ${isOwner}, isDelegate = ${isDelegate}"/>
+                    <if>
+                        <condition>
+                            <or>
+                                <and>
+                                    <!-- The calendar is private and the user is the calendar owner -->
+                                    <if-compare field="workEffort.scopeEnumId" operator="equals" value="WES_PRIVATE"/>
+                                    <if-compare field="isOwner" operator="equals" value="true"/>
+                                </and>
+                                <and>
+                                    <!-- The calendar is confidential and the user is a delegate of the calendar -->
+                                    <if-compare field="workEffort.scopeEnumId" operator="equals" value="WES_CONFIDENTIAL"/>
+                                    <if-compare field="isDelegate" operator="equals" value="true"/>
+                                </and>
+                            </or>
+                        </condition>
+                    <then>
+                        <set field="hasPermission" value="true" type="Boolean"/>
+                    </then>
+                    </if>
+                <else>
+                    <!-- RFC 2445 3.5 Only an ORGANIZER can update a VEVENT or VTODO. -->
+                    <log level="verbose" message="Checking work effort update permission, isOrganizer = ${isOrganizer}"/>
+                    <if-compare field="isOrganizer" operator="equals" value="true">
+                        <set field="hasPermission" value="true" type="Boolean"/>
+                    </if-compare>
+                </else>
+                </if-compare>
+            </if-not-empty>
+            <field-to-result field="hasPermission"/>
+        </if-compare>
+    </simple-method>
+
 </simple-methods>

Modified: ofbiz/trunk/applications/workeffort/script/org/ofbiz/workeffort/workeffort/WorkEffortSimpleServices.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/workeffort/script/org/ofbiz/workeffort/workeffort/WorkEffortSimpleServices.xml?rev=787927&r1=787926&r2=787927&view=diff
==============================================================================
--- ofbiz/trunk/applications/workeffort/script/org/ofbiz/workeffort/workeffort/WorkEffortSimpleServices.xml (original)
+++ ofbiz/trunk/applications/workeffort/script/org/ofbiz/workeffort/workeffort/WorkEffortSimpleServices.xml Wed Jun 24 06:58:54 2009
@@ -1472,4 +1472,66 @@
         <store-value value-field="lookedUpValue"/>
     </simple-method>
 
+    <!-- iCalendar services -->
+
+    <simple-method method-name="getICalWorkEfforts" short-description="Get All Work Efforts Related To An iCalendar Publish Point">
+        <!-- Servlet already confirmed workEffortId is a valid publish point -->
+        <set field="workEffortId" from-field="parameters.workEffortId"/>
+        <entity-condition list="assignedParties" entity-name="WorkEffortPartyAssignment" filter-by-date="true">
+            <condition-list combine="and">
+                <condition-expr field-name="workEffortId" from-field="workEffortId"/>
+                <!-- DELEGATE has special meaning in publish properties - it's
+                    another party in an ORGANIZER role, NOT a party whose work efforts
+                    are included in a calendar. We need another role to define calendar
+                    members. -->
+                <condition-expr field-name="roleTypeId" value="CAL_DELEGATE" operator="not-equals"/>
+            </condition-list>
+        </entity-condition>
+        <iterate entry="assignedParty" list="assignedParties">
+            <entity-condition list="resultList" entity-name="WorkEffortAndPartyAssign" filter-by-date="true">
+                <condition-list combine="and">
+                    <condition-expr field-name="scopeEnumId" value="WES_PUBLIC"/>
+                    <condition-expr field-name="workEffortTypeId" value="PUBLISH_PROPS" operator="not-equals"/>
+                    <condition-expr field-name="partyId" from-field="assignedParty.partyId"/>
+                </condition-list>
+            </entity-condition>
+            <list-to-list list="resultList" to-list="workEfforts"/>
+        </iterate>
+        <entity-and list="assignedFixedAssets" entity-name="WorkEffortFixedAssetAssign" filter-by-date="true">
+            <field-map field-name="workEffortId" from-field="workEffortId"/>
+        </entity-and>
+        <iterate entry="assignedFixedAsset" list="assignedFixedAssets">
+            <entity-condition list="resultList" entity-name="WorkEffortAndFixedAssetAssign" filter-by-date="true">
+                <condition-list combine="and">
+                    <condition-expr field-name="scopeEnumId" value="WES_PUBLIC"/>
+                    <condition-expr field-name="workEffortTypeId" value="PUBLISH_PROPS" operator="not-equals"/>
+                    <condition-expr field-name="fixedAssetId" from-field="assignedFixedAsset.fixedAssetId"/>
+                </condition-list>
+            </entity-condition>
+            <list-to-list list="resultList" to-list="workEfforts"/>
+        </iterate>
+        <entity-and list="resultList" entity-name="WorkEffortAssocToView" filter-by-date="true">
+            <field-map field-name="workEffortIdFrom" from-field="workEffortId"/>
+        </entity-and>
+        <list-to-list list="resultList" to-list="workEfforts"/>
+        <field-to-result field="workEfforts"/>
+    </simple-method>
+    
+    <simple-method method-name="getPartyICalUri" short-description="Get The Party iCalendar URI">
+        <!-- RFC 2445 4.8.4.1 and 4.8.4.3 Value must be a URI (4.3.3) -->
+        <!-- For now we just look for a primary email address. This could be expanded later. -->
+        <set field="partyId" from-field="parameters.partyId"/>
+        <entity-condition list="emailAddresses" entity-name="PartyContactWithPurpose">
+            <condition-list combine="and">
+                <condition-expr field-name="partyId" from-field="partyId"/>
+                <condition-expr field-name="contactMechPurposeTypeId" value="PRIMARY_EMAIL"/>
+                <condition-expr field-name="purposeThruDate" from-field="null"/>
+            </condition-list>
+        </entity-condition>
+        <if-compare field="util:size(emailAddresses)" operator="not-equals" value="0" type="Integer">
+            <set field="iCalUri" value="MAILTO:${emailAddresses[0].infoString}"/>
+            <field-to-result field="iCalUri"/>
+        </if-compare>
+    </simple-method>
+
 </simple-methods>

Modified: ofbiz/trunk/applications/workeffort/servicedef/services.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/workeffort/servicedef/services.xml?rev=787927&r1=787926&r2=787927&view=diff
==============================================================================
--- ofbiz/trunk/applications/workeffort/servicedef/services.xml (original)
+++ ofbiz/trunk/applications/workeffort/servicedef/services.xml Wed Jun 24 06:58:54 2009
@@ -662,24 +662,44 @@
         <auto-attributes mode="IN" include="pk" optional="false"/>
     </service>
 
-    <!-- WorkEffort Attribute Services -->
-    <service name="createWorkEffortAttribute" default-entity-name="WorkEffortAttribute" engine="entity-auto" invoke="create" auth="true">
-        <description>Create a WorkEffort Attribute</description>
-        <permission-service service-name="workEffortGenericPermission" main-action="CREATE"/>
+    <!-- WorkEffort iCalendar Services -->
+    <service name="createWorkEffortICalData" default-entity-name="WorkEffortIcalData" engine="entity-auto" invoke="create" auth="true">
+        <description>Create WorkEffort iCalendar Data</description>
+        <permission-service service-name="workEffortICalendarPermission" main-action="CREATE"/>
         <auto-attributes include="pk" mode="IN" optional="false"/>
         <auto-attributes include="nonpk" mode="IN" optional="true"/>
     </service>
-    <service name="updateWorkEffortAttribute" default-entity-name="WorkEffortAttribute" engine="entity-auto" invoke="update" auth="true">
-        <description>Update a WorkEffort Attribute</description>
-        <permission-service service-name="workEffortGenericPermission" main-action="UPDATE"/>
+    <service name="updateWorkEffortICalData" default-entity-name="WorkEffortIcalData" engine="entity-auto" invoke="update" auth="true">
+        <description>Update WorkEffort iCalendar Data</description>
+        <permission-service service-name="workEffortICalendarPermission" main-action="UPDATE"/>
         <auto-attributes include="pk" mode="IN" optional="false"/>
         <auto-attributes include="nonpk" mode="IN" optional="true"/>
     </service>
-    <service name="deleteWorkEffortAttribute" default-entity-name="WorkEffortAttribute" engine="entity-auto" invoke="delete" auth="true">
-        <description>Delete a WorkEffort Attribute</description>
-        <permission-service service-name="workEffortGenericPermission" main-action="DELETE"/>
+    <service name="deleteWorkEffortICalData" default-entity-name="WorkEffortIcalData" engine="entity-auto" invoke="delete" auth="true">
+        <description>Delete WorkEffort iCalendar Data</description>
+        <permission-service service-name="workEffortICalendarPermission" main-action="DELETE"/>
         <auto-attributes include="pk" mode="IN" optional="false"/>
     </service>
+    <service name="workEffortICalendarPermission" engine="simple"
+             location="component://workeffort/script/org/ofbiz/workeffort/permission/WorkEffortPermissionServices.xml" invoke="workEffortICalendarPermission">
+        <description>iCalendar Permission Check</description>
+        <implements service="permissionInterface"/>
+        <attribute type="String" mode="IN" name="workEffortId" optional="false"/>
+    </service>
+    <service name="getICalWorkEfforts" engine="simple"
+             location="component://workeffort/script/org/ofbiz/workeffort/workeffort/WorkEffortSimpleServices.xml" invoke="getICalWorkEfforts">
+        <description>Get iCalendar Work Efforts</description>
+        <!-- No permission checking - the servlet handles that -->
+        <attribute type="String" mode="IN" name="workEffortId" optional="false"/>
+        <attribute type="List" mode="OUT" name="workEfforts"/>
+    </service>
+    <service name="getPartyICalUri" engine="simple"
+             location="component://workeffort/script/org/ofbiz/workeffort/workeffort/WorkEffortSimpleServices.xml" invoke="getPartyICalUri">
+        <description>Get Party iCalendar URI</description>
+        <!-- No permission checking - the servlet handles that -->
+        <attribute type="String" mode="IN" name="partyId" optional="false"/>
+        <attribute type="String" mode="OUT" name="iCalUri" optional="true"/>
+    </service>
 
     <!-- WorkEffort Event Reminder Services -->
     <service name="createWorkEffortEventReminder" default-entity-name="WorkEffortEventReminder" engine="entity-auto" invoke="create" auth="true">

Added: ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalConverter.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalConverter.java?rev=787927&view=auto
==============================================================================
--- ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalConverter.java (added)
+++ ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalConverter.java Wed Jun 24 06:58:54 2009
@@ -0,0 +1,878 @@
+/*******************************************************************************
+ * 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.ofbiz.workeffort.workeffort;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.StringReader;
+import java.net.URISyntaxException;
+import java.sql.Timestamp;
+import java.text.ParseException;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javolution.util.FastList;
+import javolution.util.FastMap;
+import javolution.util.FastSet;
+
+import net.fortuna.ical4j.data.CalendarBuilder;
+import net.fortuna.ical4j.data.ParserException;
+import net.fortuna.ical4j.model.*;
+import net.fortuna.ical4j.model.component.*;
+import net.fortuna.ical4j.model.parameter.*;
+import net.fortuna.ical4j.model.property.*;
+
+import org.ofbiz.base.util.DateRange;
+import org.ofbiz.base.util.Debug;
+import org.ofbiz.base.util.ObjectType;
+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.EntityExpr;
+import org.ofbiz.entity.condition.EntityOperator;
+import org.ofbiz.entity.util.EntityUtil;
+import org.ofbiz.service.GenericServiceException;
+import org.ofbiz.service.LocalDispatcher;
+import org.ofbiz.service.ModelParam;
+import org.ofbiz.service.ModelService;
+import org.ofbiz.service.ServiceUtil;
+import org.ofbiz.service.calendar.TemporalExpression;
+import org.ofbiz.service.calendar.TemporalExpressionWorker;
+
+/** 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 contactMechIdXParamName = "X-ORG-APACHE-OFBIZ-CONTACT-MECH-ID";
+    protected static final String partyIdXParamName = "X-ORG-APACHE-OFBIZ-PARTY-ID";
+    protected static final ProdId prodId = new ProdId("-//Apache Open For Business//Work Effort Calendar//EN");
+    protected static final String workEffortIdParamName = "X-ORG-APACHE-OFBIZ-WORKEFFORT-ID";
+    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 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;
+    }
+
+    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 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((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 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;
+    }
+
+    @SuppressWarnings("unchecked")
+    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()));
+        GenericDelegator delegator = workEffort.getDelegator();
+        String workEffortId = workEffort.getString("workEffortId");
+        List<GenericValue> reminderList = delegator.findList("WorkEffortEventReminder", EntityCondition.makeCondition("workEffortId", EntityOperator.EQUALS, workEffort.get("workEffortId")), null, null, null, false);
+        for (GenericValue reminder : reminderList) {
+            String reminderId = workEffortId + "-" + reminder.getString("sequenceId");
+            VAlarm alarm = null;
+            PropertyList alarmProps = null;
+            boolean newAlarm = true;
+            Iterator<VAlarm> i = 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");
+            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);
+                }
+            }
+        }
+    }
+
+    protected static void getPartyPrimaryEmailAddress(Property property, GenericValue partyAssign, Map<String, Object> context) {
+        Map<String, ? extends Object> serviceMap = UtilMisc.toMap("partyId", partyAssign.get("partyId"));
+        Map<String, Object> resultMap = invokeService("getPartyICalUri", serviceMap, context);
+        String iCalUri = (String) resultMap.get("iCalUri");
+        if (iCalUri != null) {
+            try {
+                property.setValue(iCalUri);
+            } catch (Exception e) {
+                Debug.logError(e, "Error while setting party URI: ", module);
+            }
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    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 = (List) resultMap.get("workEfforts");
+        if (workEfforts != null) {
+            return WorkEffortWorker.removeDuplicateWorkEfforts(workEfforts);
+        }
+        return null;
+    }
+
+    protected static Map<String, Object> invokeService(String serviceName, Map<String, ? extends Object> serviceMap, Map<String, Object> context) {
+        LocalDispatcher dispatcher = (LocalDispatcher) context.get("dispatcher");
+        Map<String, Object> localMap = FastMap.newInstance();
+        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 = "Error while creating service Map for service " + serviceName + ": ";
+            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 = "Error while invoking service " + serviceName + ": ";
+            Debug.logError(e, errMsg, module);
+            return ServiceUtil.returnError(errMsg + e);
+        }
+    }
+
+    protected static void loadPartyAssignment(Property property, GenericValue partyAssign, Map<String, Object> context) {
+        getPartyPrimaryEmailAddress(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:[hidden email]");
+            } 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")));
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    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 = 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")));
+        replaceProperty(componentProps, toUid(workEffort.getString("workEffortId")));
+        replaceProperty(componentProps, toXProperty(workEffortIdXPropName, workEffort.getString("workEffortId")));
+    }
+
+    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 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());
+    }
+
+    @SuppressWarnings("unchecked")
+    public static void storeCalendar(InputStream is, Map<String, Object> context) throws IOException, ParserException, GenericEntityException, GenericServiceException {
+        CalendarBuilder builder = new CalendarBuilder();
+        Calendar calendar = null;
+        try {
+            calendar = builder.build(is);
+        } catch (IOException e) {
+            Debug.logError(e, "Error while updating calendar: ", module);
+            throw e;
+        } finally {
+            if (is != null) {
+                is.close();
+            }
+        }
+        Debug.logInfo("Processing calendar:\r\n" + calendar, module);
+        String workEffortId = fromXProperty(calendar.getProperties(), workEffortIdXPropName);
+        if (workEffortId == null) {
+            // TODO: Create new publish point
+            Debug.logWarning("Warning: Not an OFBiz calendar: \r\n" + calendar, module);
+            return;
+        }
+        if (!workEffortId.equals(context.get("workEffortId"))) {
+            Debug.logWarning("Spoof attempt: received calendar workEffortId " + workEffortId +
+                    " on URL workEffortId " + context.get("workEffortId"), module);
+            return;
+        }
+        Map<String, ? extends Object> serviceMap = UtilMisc.toMap("workEffortId", workEffortId, "icalData", calendar.toString());
+        GenericDelegator delegator = (GenericDelegator) context.get("delegator");
+        GenericValue publishProperties = delegator.findOne("WorkEffort", UtilMisc.toMap("workEffortId", workEffortId), false);
+        if (!isCalendarPublished(publishProperties)) {
+            Debug.logInfo("WorkEffort calendar is not published: " + workEffortId, module);
+            return;
+        }
+        GenericValue iCalData = publishProperties.getRelatedOne("WorkEffortIcalData");
+        if (iCalData == null) {
+            invokeService("createWorkEffortICalData", serviceMap, context);
+        } else {
+            invokeService("updateWorkEffortICalData", serviceMap, context);
+        }
+        List<GenericValue> workEfforts = getRelatedWorkEfforts(publishProperties, context);
+        if (workEfforts == null || workEfforts.size() == 0) {
+            return;
+        }
+        // Security issue: make sure only related work efforts get updated
+        Set validWorkEfforts = FastSet.newInstance();
+        for (GenericValue workEffort : workEfforts) {
+            validWorkEfforts.add(workEffort.getString("workEffortId"));
+        }
+        List<Component> components = calendar.getComponents();
+        for (Component component : components) {
+            if (Component.VEVENT.equals(component.getName()) || Component.VTODO.equals(component.getName())) {
+                workEffortId = fromXProperty(component.getProperties(), workEffortIdXPropName);
+                if (workEffortId != null) {
+                    if (validWorkEfforts.contains(workEffortId)) {
+                        storeWorkEffort(component, context);
+                    } else {
+                        Debug.logWarning("Spoof attempt: unrelated workEffortId " + workEffortId +
+                                " on URL workEffortId " + context.get("workEffortId"), module);
+                        continue;
+                    }
+                }
+            }
+        }
+    }
+
+    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;
+        }
+    }
+
+    /** 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 An iCalendar as a <code>String</code>, or <code>null</code>
+     * if <code>workEffortId</code> is invalid.
+     * @throws GenericEntityException
+     */
+    public static String getICalendar(String workEffortId, Map<String, Object> context) throws GenericEntityException {
+        GenericDelegator delegator = (GenericDelegator) context.get("delegator");
+        GenericValue publishProperties = delegator.findOne("WorkEffort", UtilMisc.toMap("workEffortId", workEffortId), false);
+        if (!isCalendarPublished(publishProperties)) {
+            Debug.logInfo("WorkEffort calendar is not published: " + workEffortId, module);
+            return null;
+        }
+        if (!"WES_PUBLIC".equals(publishProperties.get("scopeEnumId")) && !hasPermission(workEffortId, "VIEW", context)) {
+            return null;
+        }
+        Calendar calendar = makeCalendar(publishProperties, context);
+        ComponentList components = calendar.getComponents();
+        List<GenericValue> workEfforts = getRelatedWorkEfforts(publishProperties, context);
+        if (workEfforts != null) {
+            for (GenericValue workEffort : workEfforts) {
+                toCalendarComponent(components, workEffort, context);
+            }
+        }
+        if (Debug.verboseOn()) {
+            try {
+                calendar.validate(true);
+                Debug.logVerbose("iCalendar passes validation", module);
+            } catch (ValidationException e) {
+                Debug.logVerbose("iCalendar fails validation: " + e, module);
+            }
+        }
+        // TODO: Remove this before commit
+            try {
+                calendar.validate(true);
+                Debug.logInfo("iCalendar passes validation", module);
+            } catch (ValidationException e) {
+                Debug.logInfo("iCalendar fails validation: " + e, module);
+            }
+        return calendar.toString();
+    }
+
+    protected static Calendar makeCalendar(GenericValue workEffort, Map<String, Object> context) throws GenericEntityException {
+        String iCalData = null;
+        GenericValue iCalValue = workEffort.getRelatedOne("WorkEffortIcalData");
+        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 void storeWorkEffort(Component component, Map<String, Object> context) throws GenericEntityException, GenericServiceException {
+        PropertyList propertyList = component.getProperties();
+        String workEffortId = fromXProperty(propertyList, workEffortIdXPropName);
+        GenericDelegator delegator = (GenericDelegator) context.get("delegator");
+        GenericValue workEffort = delegator.findOne("WorkEffort", UtilMisc.toMap("workEffortId", workEffortId), false);
+        if (workEffort == null) {
+            return;
+        }
+        if (!hasPermission(workEffortId, "UPDATE", context)) {
+            return;
+        }
+        Map<String, Object> serviceMap = FastMap.newInstance();
+        serviceMap.put("workEffortId", workEffortId);
+        setMapElement(serviceMap, "scopeEnumId", fromClazz(propertyList));
+        setMapElement(serviceMap, "description", fromDescription(propertyList));
+        setMapElement(serviceMap, "estimatedStartDate", fromDtStart(propertyList));
+        setMapElement(serviceMap, "estimatedMilliSeconds", fromDuration(propertyList));
+        setMapElement(serviceMap, "lastModifiedDate", fromLastModified(propertyList));
+        setMapElement(serviceMap, "locationDesc", fromLocation(propertyList));
+        setMapElement(serviceMap, "priority", fromPriority(propertyList));
+        setMapElement(serviceMap, "currentStatusId", fromStatus(propertyList));
+        setMapElement(serviceMap, "workEffortName", fromSummary(propertyList));
+        if ("VTODO".equals(component.getName())) {
+            setMapElement(serviceMap, "actualCompletionDate", fromCompleted(propertyList));
+            setMapElement(serviceMap, "percentComplete", fromPercentComplete(propertyList));
+        } else {
+            setMapElement(serviceMap, "estimatedCompletionDate", fromDtEnd(propertyList));
+        }
+        invokeService("updateWorkEffort", serviceMap, context);
+    }
+
+    @SuppressWarnings("unchecked")
+    protected static void toCalendarComponent(ComponentList components, GenericValue workEffort, Map<String, Object> context) throws GenericEntityException {
+        GenericDelegator delegator = workEffort.getDelegator();
+        String workEffortId = workEffort.getString("workEffortId");
+        context.put("workEffortId", workEffortId);
+        String workEffortTypeId = workEffort.getString("workEffortTypeId");
+        GenericValue typeValue = delegator.findOne("WorkEffortType", UtilMisc.toMap("workEffortTypeId", workEffortTypeId), true);
+        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;
+        }
+        Iterator<Component> i = resultList.iterator();
+        while (i.hasNext()) {
+            result = i.next();
+            Property xProperty = result.getProperty(workEffortIdXPropName);
+            if (xProperty != null && workEffortId.equals(xProperty.getValue())) {
+                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 = EntityUtil.filterByDate(delegator.findList("WorkEffortPartyAssignView", EntityCondition.makeCondition("workEffortId", EntityOperator.EQUALS, workEffortId), null, null, null, true));
+        if (relatedParties.size() > 0) {
+            loadRelatedParties(relatedParties, componentProps, context);
+        }
+        if (newComponent) {
+            DateRange range = new DateRange(workEffort.getTimestamp("estimatedStartDate"), workEffort.getTimestamp("estimatedCompletionDate"));
+            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));
+                    }
+                }
+            } else {
+                componentProps.add(new DtEnd(new DateTime(range.end())));
+            }
+            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);
+            }
+        }
+    }
+
+    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.fromLong(javaObj.longValue());
+        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);
+    }
+}

Propchange: ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalConverter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalConverter.java
------------------------------------------------------------------------------
    svn:keywords = "Date Rev Author URL Id"

Propchange: ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalConverter.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalHandlerFactory.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalHandlerFactory.java?rev=787927&view=auto
==============================================================================
--- ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalHandlerFactory.java (added)
+++ ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalHandlerFactory.java Wed Jun 24 06:58:54 2009
@@ -0,0 +1,110 @@
+/*******************************************************************************
+ * 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.ofbiz.workeffort.workeffort;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import javolution.util.FastMap;
+
+import org.ofbiz.base.util.Debug;
+import org.ofbiz.webapp.webdav.RequestHandler;
+import org.ofbiz.webapp.webdav.RequestHandlerFactory;
+
+/** WebDAV request handler factory for iCalendar. This class is a simple connector
+ * between the WebDAV servlet and the <code>ICalWorker</code> class.
+ */
+@SuppressWarnings("serial")
+public class ICalHandlerFactory implements RequestHandlerFactory {
+    
+    public static final String module = ICalHandlerFactory.class.getName();
+    
+    protected final Map<String, RequestHandler> handlerMap;
+    protected final RequestHandler invalidMethodHandler = new InvalidMethodHandler();
+    protected final RequestHandler doNothingHandler = new DoNothingHandler();
+
+    public ICalHandlerFactory() {
+        this.handlerMap = FastMap.newInstance();
+        this.handlerMap.put("COPY", doNothingHandler);
+        this.handlerMap.put("DELETE", doNothingHandler);
+        this.handlerMap.put("GET", new GetHandler());
+        this.handlerMap.put("HEAD", doNothingHandler);
+        this.handlerMap.put("LOCK", doNothingHandler);
+        this.handlerMap.put("MKCOL", doNothingHandler);
+        this.handlerMap.put("MOVE", doNothingHandler);
+        this.handlerMap.put("POST", doNothingHandler);
+        this.handlerMap.put("PROPFIND", new PropFindHandler());
+        this.handlerMap.put("PROPPATCH", doNothingHandler);
+        this.handlerMap.put("PUT", new PutHandler());
+        this.handlerMap.put("UNLOCK", doNothingHandler);
+    }
+
+    public RequestHandler getHandler(String method) {
+        RequestHandler handler = this.handlerMap.get(method);
+        if (handler == null) {
+            return invalidMethodHandler;
+        }
+        return handler;
+    }
+
+    protected static class InvalidMethodHandler implements RequestHandler {
+        public void handleRequest(HttpServletRequest request, HttpServletResponse response, ServletContext context) throws ServletException, IOException {
+            Debug.logInfo("[InvalidMethodHandler] method = " + request.getMethod(), module);
+            response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
+        }
+    }
+
+    protected static class DoNothingHandler implements RequestHandler {
+        public void handleRequest(HttpServletRequest request, HttpServletResponse response, ServletContext context) throws ServletException, IOException {
+            Debug.logInfo("[DoNothingHandler] method = " + request.getMethod(), module);
+            response.setStatus(HttpServletResponse.SC_OK);
+        }
+    }
+
+    protected static class GetHandler implements RequestHandler {
+        public void handleRequest(HttpServletRequest request, HttpServletResponse response, ServletContext context) throws ServletException, IOException {
+            Debug.logInfo("[GetHandler] starting request", module);
+            ICalWorker.handleGetRequest(request, response, context);
+            Debug.logInfo("[GetHandler] finished request", module);
+        }
+    }
+
+    protected static class PutHandler implements RequestHandler {
+        public void handleRequest(HttpServletRequest request, HttpServletResponse response, ServletContext context) throws ServletException, IOException {
+            Debug.logInfo("[PutHandler] starting request", module);
+            ICalWorker.handlePutRequest(request, response, context);
+            Debug.logInfo("[PutHandler] finished request", module);
+        }
+    }
+
+    protected static class PropFindHandler implements RequestHandler {
+        public void handleRequest(HttpServletRequest request, HttpServletResponse response, ServletContext context) throws ServletException, IOException {
+            Debug.logInfo("[PropFindHandler] starting request", module);
+            ICalWorker.handlePropFindRequest(request, response, context);
+            Debug.logInfo("[PropFindHandler] finished request", module);
+        }
+    }
+
+};

Propchange: ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalHandlerFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalHandlerFactory.java
------------------------------------------------------------------------------
    svn:keywords = "Date Rev Author URL Id"

Propchange: ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalHandlerFactory.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain