You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by ad...@apache.org on 2008/11/18 19:56:12 UTC

svn commit: r718684 - in /ofbiz/trunk: ./ applications/workeffort/ applications/workeffort/data/ applications/workeffort/entitydef/ applications/workeffort/src/org/ofbiz/workeffort/workeffort/ applications/workeffort/webapp/ical/ applications/workeffor...

Author: adrianc
Date: Tue Nov 18 10:56:11 2008
New Revision: 718684

URL: http://svn.apache.org/viewvc?rev=718684&view=rev
Log:
A rudimentary iCalendar integration implementation. Work efforts can be published in iCalendar format. The published calendar is read-only.

Run ant run-install, then navigate to:

https://localhost:8443/iCalendar/CALENDAR_PUB_DEMO/MyCalendar.ics

for a demonstration.

I will continue to build this out as I have time.

Added:
    ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalRecurConverter.java   (with props)
    ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalServlet.java   (with props)
    ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalendarWorker.java   (with props)
    ofbiz/trunk/applications/workeffort/webapp/ical/
    ofbiz/trunk/applications/workeffort/webapp/ical/WEB-INF/
    ofbiz/trunk/applications/workeffort/webapp/ical/WEB-INF/web.xml   (with props)
    ofbiz/trunk/framework/base/lib/ical4j-1.0-beta5.jar   (with props)
Modified:
    ofbiz/trunk/.classpath
    ofbiz/trunk/LICENSE
    ofbiz/trunk/applications/workeffort/build.xml
    ofbiz/trunk/applications/workeffort/data/WorkEffortTypeData.xml
    ofbiz/trunk/applications/workeffort/entitydef/entitymodel_view.xml
    ofbiz/trunk/applications/workeffort/ofbiz-component.xml
    ofbiz/trunk/specialpurpose/projectmgr/data/ProjectMgrDemoData.xml

Modified: ofbiz/trunk/.classpath
URL: http://svn.apache.org/viewvc/ofbiz/trunk/.classpath?rev=718684&r1=718683&r2=718684&view=diff
==============================================================================
--- ofbiz/trunk/.classpath (original)
+++ ofbiz/trunk/.classpath Tue Nov 18 10:56:11 2008
@@ -90,6 +90,7 @@
 	<classpathentry kind="lib" path="framework/geronimo/lib/geronimo-transaction-2.1.1.jar"/>
 	<classpathentry kind="lib" path="framework/entity/lib/ofbiz-minerva.jar"/>
 	<classpathentry kind="lib" path="framework/entity/lib/commons-dbcp-1.3-20080708-r674758.jar"/>	
+	<classpathentry kind="lib" path="framework/base/lib/ical4j-1.0-beta5.jar"/>
 	<classpathentry kind="lib" path="framework/base/lib/Tidy.jar"/>
 	<classpathentry kind="lib" path="framework/base/lib/mx4j-remote-3.0.1.jar"/>
 	<classpathentry kind="lib" path="framework/base/lib/mx4j-3.0.1.jar"/>

Modified: ofbiz/trunk/LICENSE
URL: http://svn.apache.org/viewvc/ofbiz/trunk/LICENSE?rev=718684&r1=718683&r2=718684&view=diff
==============================================================================
--- ofbiz/trunk/LICENSE (original)
+++ ofbiz/trunk/LICENSE Tue Nov 18 10:56:11 2008
@@ -420,21 +420,22 @@
 =========================================================================
 The following libraries distributed with Apache OFBiz are licensed under the 
 BSD License:
-ofbiz/trunk/framework/base/lib/javacc/javacc-4.1.jar
-ofbiz/trunk/framework/base/lib/httpunit.jar
 ofbiz/trunk/framework/base/lib/freemarker-2.3.10.jar
+ofbiz/trunk/framework/base/lib/httpunit.jar
+ofbiz/trunk/framework/base/lib/ical4j-1.0-beta5.jar
+ofbiz/trunk/framework/base/lib/javacc/javacc-4.1.jar
+ofbiz/trunk/framework/base/lib/javolution-5.2.3.jar
 ofbiz/trunk/framework/base/lib/junitperf.jar
 ofbiz/trunk/framework/base/lib/scripting/antlr-2.7.6.jar
 ofbiz/trunk/framework/base/lib/scripting/asm-2.2.jar
 ofbiz/trunk/framework/base/lib/scripting/asm-analysis-2.2.jar
 ofbiz/trunk/framework/base/lib/scripting/asm-tree-2.2.jar
 ofbiz/trunk/framework/base/lib/scripting/asm-util-2.2.jar
-ofbiz/trunk/specialpurpose/pos/lib/looks-2.0.2.jar
-ofbiz/trunk/framework/base/lib/javolution-5.2.3.jar
-ofbiz/trunk/framework/images/webapp/images/dojo/*
-ofbiz/trunk/specialpurpose/ldap/lib/cas-server-core-3.3.jar
 ofbiz/trunk/framework/images/webapp/images/jsgantt.css
 ofbiz/trunk/framework/images/webapp/images/jsgantt.js
+ofbiz/trunk/framework/images/webapp/images/dojo/*
+ofbiz/trunk/specialpurpose/ldap/lib/cas-server-core-3.3.jar
+ofbiz/trunk/specialpurpose/pos/lib/looks-2.0.2.jar
 
 =========================================================================
 The BSD License

Modified: ofbiz/trunk/applications/workeffort/build.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/workeffort/build.xml?rev=718684&r1=718683&r2=718684&view=diff
==============================================================================
--- ofbiz/trunk/applications/workeffort/build.xml (original)
+++ ofbiz/trunk/applications/workeffort/build.xml Tue Nov 18 10:56:11 2008
@@ -35,6 +35,7 @@
         <fileset dir="../../framework/base/lib/j2eespecs" includes="*.jar"/>
         <fileset dir="../../framework/base/lib/scripting" includes="*.jar"/>
         <fileset dir="../../framework/base/build/lib" includes="*.jar"/>
+        <fileset dir="../../framework/catalina/lib" includes="*.jar"/>
         <fileset dir="../../framework/entity/lib" includes="*.jar"/>
         <fileset dir="../../framework/entity/build/lib" includes="*.jar"/>
         <fileset dir="../../framework/security/build/lib" includes="*.jar"/>

Modified: ofbiz/trunk/applications/workeffort/data/WorkEffortTypeData.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/workeffort/data/WorkEffortTypeData.xml?rev=718684&r1=718683&r2=718684&view=diff
==============================================================================
--- ofbiz/trunk/applications/workeffort/data/WorkEffortTypeData.xml (original)
+++ ofbiz/trunk/applications/workeffort/data/WorkEffortTypeData.xml Tue Nov 18 10:56:11 2008
@@ -180,7 +180,8 @@
     <WorkEffortType description="Business Travel" hasTable="N" parentTypeId="EVENT" workEffortTypeId="BUSINESS_TRAVEL"/>
     <WorkEffortType description="Meeting" hasTable="N" parentTypeId="EVENT" workEffortTypeId="MEETING"/>
     <WorkEffortType description="Personal Time Off" hasTable="N" parentTypeId="EVENT" workEffortTypeId="PERSONAL_TIMEOFF"/>    
-    
+    <WorkEffortType description="Publish Properties" hasTable="N" workEffortTypeId="PUBLISH_PROPS"/>    
+
     <!-- Routing status, (workEffort Template) -->
     <StatusType description="Manufacturing Task and Routing status" hasTable="N" parentTypeId="WORK_EFFORT_STATUS" statusTypeId="ROUTING_STATUS"/>
     

Modified: ofbiz/trunk/applications/workeffort/entitydef/entitymodel_view.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/workeffort/entitydef/entitymodel_view.xml?rev=718684&r1=718683&r2=718684&view=diff
==============================================================================
--- ofbiz/trunk/applications/workeffort/entitydef/entitymodel_view.xml (original)
+++ ofbiz/trunk/applications/workeffort/entitydef/entitymodel_view.xml Tue Nov 18 10:56:11 2008
@@ -258,6 +258,30 @@
         <key-map field-name="workEffortIdFrom" rel-field-name="workEffortId"/>
       </relation>
     </view-entity>
+    <view-entity entity-name="WorkEffortAssocToView"
+            package-name="org.ofbiz.workeffort.workeffort"
+            title="Work Effort Association To (Child) View">
+      <member-entity entity-alias="WEA" entity-name="WorkEffortAssoc"/>
+      <member-entity entity-alias="WETO" entity-name="WorkEffort"/>
+      <alias-all entity-alias="WEA"/>
+      <alias-all entity-alias="WETO"/>
+      <view-link entity-alias="WEA" rel-entity-alias="WETO">
+        <key-map field-name="workEffortIdTo" rel-field-name="workEffortId"/>
+      </view-link>
+    </view-entity>
+    <!--
+    <view-entity entity-name="WorkEffortAssocFromView"
+            package-name="org.ofbiz.workeffort.workeffort"
+            title="Work Effort Association From (Parent) View">
+      <member-entity entity-alias="WEA" entity-name="WorkEffortAssoc"/>
+      <member-entity entity-alias="WEFR" entity-name="WorkEffort"/>
+      <alias-all entity-alias="WEA"/>
+      <alias-all entity-alias="WEFR"/>
+      <view-link entity-alias="WEA" rel-entity-alias="WEFR">
+        <key-map field-name="workEffortIdFrom" rel-field-name="workEffortId"/>
+      </view-link>
+    </view-entity>
+    -->
     <view-entity entity-name="WorkEffortNoteAndData"
             package-name="org.ofbiz.workeffort.workeffort"
             title="Work Effort Note And Note Data Entity">

Modified: ofbiz/trunk/applications/workeffort/ofbiz-component.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/workeffort/ofbiz-component.xml?rev=718684&r1=718683&r2=718684&view=diff
==============================================================================
--- ofbiz/trunk/applications/workeffort/ofbiz-component.xml (original)
+++ ofbiz/trunk/applications/workeffort/ofbiz-component.xml Tue Nov 18 10:56:11 2008
@@ -43,4 +43,11 @@
         location="webapp/workeffort"
         base-permission="OFBTOOLS,WORKEFFORTMGR"
         mount-point="/workeffort"/>
+
+     <webapp name="ical"
+        title="iCalendar"
+        app-bar-display="false"
+        server="default-server"
+        location="webapp/ical"
+        mount-point="/iCalendar"/>
 </ofbiz-component>

Added: ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalRecurConverter.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalRecurConverter.java?rev=718684&view=auto
==============================================================================
--- ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalRecurConverter.java (added)
+++ ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalRecurConverter.java Tue Nov 18 10:56:11 2008
@@ -0,0 +1,293 @@
+/*******************************************************************************
+ * 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.util.List;
+import java.util.Set;
+import java.util.Stack;
+
+import javolution.util.FastList;
+import javolution.util.FastSet;
+
+import net.fortuna.ical4j.model.*;
+import net.fortuna.ical4j.model.property.*;
+
+import org.ofbiz.service.calendar.TemporalExpression;
+import org.ofbiz.service.calendar.TemporalExpressions;
+import org.ofbiz.service.calendar.TemporalExpressionVisitor;
+import org.ofbiz.service.calendar.TemporalExpressions.*;
+
+/** Temporal Expression to iCalendar recurrence converter. The conversion results
+ * (or conversion success) are unpredictable since the OFBiz Temporal Expressions
+ * are more sophisticated than iCalendar recurrences. This class attempts to
+ * make a best attempt at conversion and throws <code>IllegalStateException</code>
+ * when conversion is not possible. 
+ */
+public class ICalRecurConverter implements TemporalExpressionVisitor {
+    protected static final WeekDay dayOfWeekArray[] = {WeekDay.SU, WeekDay.MO, WeekDay.TU, WeekDay.WE, WeekDay.TH, WeekDay.FR, WeekDay.SA};
+    protected DtStart dateStart = null;
+    protected List<DateListProperty> incDateList = FastList.newInstance();
+    protected List<DateListProperty> exDateList = FastList.newInstance();
+    protected List<RRule> incRuleList = FastList.newInstance();
+    protected List<ExRule> exRuleList = FastList.newInstance();
+    protected VisitorState state = new VisitorState();
+    protected Stack<VisitorState> stateStack = new Stack<VisitorState>();
+
+    protected ICalRecurConverter() {}
+
+    @SuppressWarnings("unchecked")
+    public static void convert(TemporalExpression expr, PropertyList eventProps) {
+        ICalRecurConverter converter = new ICalRecurConverter();
+        expr.accept(converter);
+        DtStart dateStart = (DtStart) eventProps.getProperty(Property.DTSTART);
+        if (converter.dateStart != null) {
+            if (dateStart != null) {
+                eventProps.remove(dateStart);
+            }
+            dateStart = converter.dateStart;
+            eventProps.add(dateStart);
+        }
+        if (dateStart != null && converter.exRuleList.size() > 0) {
+            // iCalendar quirk - if exclusions exist, then the start date must be excluded also
+            ExDate exdate = new ExDate();
+            exdate.getDates().add(dateStart.getDate());
+            converter.exDateList.add(exdate);
+        }
+        eventProps.addAll(converter.incDateList);
+        eventProps.addAll(converter.incRuleList);
+        eventProps.addAll(converter.exDateList);
+        eventProps.addAll(converter.exRuleList);
+    }
+
+    // ----- TemporalExpressionVisitor Implementation ----- //
+
+    public void visit(Null expr) {}
+    
+    public void visit(Union expr) {
+        for (TemporalExpression childExpr : expr.getExpressionSet()) {
+            childExpr.accept(this);
+        }
+    }
+    
+    public void visit(Intersection expr) {
+        this.stateStack.push(this.state);
+        VisitorState newState = new VisitorState();
+        newState.isExcluded = this.state.isExcluded;
+        newState.isIntersection = true;
+        this.state = newState;
+        for (TemporalExpression childExpr : expr.getExpressionSet()) {
+            childExpr.accept(this);
+        }
+        this.state = this.stateStack.pop();
+        if (newState.inclRecurList.size() > 0) {
+            this.incRuleList.add(new RRule(this.consolidateRecurs(newState.inclRecurList)));
+        }
+        if (newState.exRecurList.size() > 0) {
+            this.exRuleList.add(new ExRule(this.consolidateRecurs(newState.exRecurList)));
+        }
+    }
+
+    public void visit(Difference expr) {
+        VisitorState newState = new VisitorState();
+        newState.isIntersection = this.state.isIntersection;
+        this.stateStack.push(this.state);
+        this.state = newState;
+        expr.getIncluded().accept(this);
+        newState.isExcluded = true;
+        expr.getExcluded().accept(this);
+        this.state = this.stateStack.pop();
+        if (this.state.isIntersection) {
+            this.state.inclRecurList.addAll(newState.inclRecurList);
+            this.state.exRecurList.addAll(newState.exRecurList);
+        }
+    }
+    
+    public void visit(TemporalExpressions.DateRange expr) {
+        if (this.state.isExcluded) {
+            throw new IllegalStateException("iCalendar does not support excluded date ranges");
+        }
+        org.ofbiz.base.util.DateRange range = expr.getDateRange();
+        PeriodList periodList = new PeriodList();
+        periodList.add(new Period(new DateTime(range.start()), new DateTime(range.end())));
+        this.incDateList.add(new RDate(periodList));
+    }
+    
+    @SuppressWarnings("unchecked")
+    public void visit(TimeOfDayRange expr) {
+        // TODO: this needs a better conversion
+        int startHr = expr.getStartHours();
+        int endHr = expr.getEndHours();
+        NumberList hourList = new NumberList();
+        hourList.add(startHr);
+        while (startHr != endHr) {
+            startHr++;
+            if (startHr == 24) {
+                startHr = 0;
+            }
+            hourList.add(startHr);
+        }
+        Recur recur = new Recur(Recur.HOURLY, 0);
+        recur.getHourList().addAll(hourList);
+        this.state.addRecur(recur);
+    }
+    
+    @SuppressWarnings("unchecked")
+    public void visit(TemporalExpressions.DayOfWeekRange expr) {
+        int startDay = expr.getStartDay();
+        int endDay = expr.getEndDay();
+        WeekDayList dayList = new WeekDayList();
+        dayList.add(dayOfWeekArray[startDay - 1]);
+        while (startDay != endDay) {
+            startDay++;
+            if (startDay > java.util.Calendar.SATURDAY) {
+                startDay = java.util.Calendar.SUNDAY;
+            }
+            dayList.add(dayOfWeekArray[startDay - 1]);
+        }
+        Recur recur = new Recur(Recur.DAILY, 0);
+        recur.getDayList().addAll(dayList);
+        this.state.addRecur(recur);
+    }
+    
+    @SuppressWarnings("unchecked")
+    public void visit(TemporalExpressions.MonthRange expr) {
+        int startMonth = expr.getStartMonth();
+        int endMonth = expr.getEndMonth();
+        java.util.Calendar cal = java.util.Calendar.getInstance();
+        int maxMonth = cal.getActualMaximum(java.util.Calendar.MONTH);
+        NumberList monthList = new NumberList();
+        monthList.add(startMonth + 1);
+        while (startMonth != endMonth) {
+            startMonth++;
+            if (startMonth > maxMonth) {
+                startMonth = java.util.Calendar.JANUARY;
+            }
+            monthList.add(startMonth + 1);
+        }
+        Recur recur = new Recur(Recur.MONTHLY, 0);
+        recur.getMonthList().addAll(monthList);
+        this.state.addRecur(recur);
+    }
+    
+    @SuppressWarnings("unchecked")
+    public void visit(TemporalExpressions.DayOfMonthRange expr) {
+        int startDay = expr.getStartDay();
+        int endDay = expr.getEndDay();
+        NumberList dayList = new NumberList();
+        dayList.add(startDay);
+        while (startDay != endDay) {
+            startDay++;
+            dayList.add(startDay);
+        }
+        Recur recur = new Recur(Recur.DAILY, 0);
+        recur.getMonthDayList().addAll(dayList);
+        this.state.addRecur(recur);
+    }
+    
+    public void visit(TemporalExpressions.DayInMonth expr) {
+        Recur recur = new Recur(Recur.MONTHLY, 0);
+        recur.getDayList().add(new WeekDay(dayOfWeekArray[expr.getDayOfWeek() - 1], expr.getOccurrence()));
+        this.state.addRecur(recur);
+    }
+    
+    public void visit(TemporalExpressions.Frequency expr) {
+        if (this.dateStart == null) {
+            this.dateStart = new DtStart(new net.fortuna.ical4j.model.Date(expr.getStartDate()));
+        }
+        int freqCount = expr.getFreqCount();
+        int freqType = expr.getFreqType();
+        switch (freqType) {
+        case java.util.Calendar.SECOND:
+            this.state.addRecur((new Recur(Recur.SECONDLY, freqCount)));
+        case java.util.Calendar.MINUTE:
+            this.state.addRecur((new Recur(Recur.MINUTELY, freqCount)));
+        case java.util.Calendar.HOUR:
+            this.state.addRecur((new Recur(Recur.HOURLY, freqCount)));
+        case java.util.Calendar.DAY_OF_MONTH:
+            this.state.addRecur((new Recur(Recur.DAILY, freqCount)));
+        case java.util.Calendar.MONTH:
+            this.state.addRecur((new Recur(Recur.MONTHLY, freqCount)));
+        case java.util.Calendar.YEAR:
+            this.state.addRecur((new Recur(Recur.YEARLY, freqCount)));
+        }
+    }
+    
+    @SuppressWarnings("unchecked")
+    protected Recur consolidateRecurs(List<Recur> recurList) {
+        // Try to consolidate a list of Recur instances into one instance
+        Set<Integer> monthList = FastSet.newInstance();
+        Set<Integer> monthDayList = FastSet.newInstance();
+        Set<WeekDay> weekDayList = FastSet.newInstance();
+        Set<Integer> hourList = FastSet.newInstance();
+        String freq = null;
+        int freqCount = 0;
+        for (Recur recur : recurList) {
+            monthList.addAll(recur.getMonthList());
+            monthDayList.addAll(recur.getMonthDayList());
+            weekDayList.addAll(recur.getDayList());
+            hourList.addAll(recur.getHourList());
+            if (recur.getInterval() != 0 && freq == null) {
+                freq = recur.getFrequency();
+                freqCount = recur.getInterval();
+            }
+        }
+        if (freq == null && monthList.size() > 0) {
+            freq = Recur.MONTHLY;
+        } else if (freq == null && (monthDayList.size() > 0 || weekDayList.size() > 0)) {
+            freq = Recur.DAILY;
+        } else if (freq == null && hourList.size() > 0) {
+            freq = Recur.HOURLY;
+        }
+        if (freq == null) {
+            throw new IllegalStateException("Unable to convert intersection");
+        }
+        Recur newRecur = new Recur(freq, 0);
+        if (freqCount != 0) {
+            newRecur.setInterval(freqCount);
+        }
+        newRecur.getMonthList().addAll(monthList);
+        newRecur.getMonthDayList().addAll(monthDayList);
+        newRecur.getDayList().addAll(weekDayList);
+        newRecur.getHourList().addAll(hourList);
+        return newRecur;
+    }
+    
+    protected class VisitorState {
+        public boolean isExcluded = false;
+        public boolean isIntersection = false;
+        public List<Recur> inclRecurList = FastList.newInstance();
+        public List<Recur> exRecurList = FastList.newInstance();
+        public void addRecur(Recur recur) {
+            if (this.isIntersection) {
+                if (this.isExcluded) {
+                    this.exRecurList.add(recur);
+                } else {
+                    this.inclRecurList.add(recur);
+                }
+            } else {
+                if (this.isExcluded) {
+                    exRuleList.add(new ExRule(recur));
+                } else {
+                    incRuleList.add(new RRule(recur));
+                }
+            }
+        }
+    }
+}

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

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

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

Added: ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalServlet.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalServlet.java?rev=718684&view=auto
==============================================================================
--- ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalServlet.java (added)
+++ ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalServlet.java Tue Nov 18 10:56:11 2008
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * 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.io.OutputStreamWriter;
+import java.io.Writer;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import net.fortuna.ical4j.model.Calendar;
+
+import org.ofbiz.base.util.Debug;
+import org.ofbiz.base.util.UtilJ2eeCompat;
+import org.ofbiz.base.util.UtilValidate;
+import org.ofbiz.entity.GenericDelegator;
+
+@SuppressWarnings("serial")
+public class ICalServlet extends HttpServlet {
+    public static final String module = ICalServlet.class.getName();
+
+    /** Initialize this servlet. */
+    public void init() throws ServletException {
+        super.init();
+        if (Debug.infoOn()) {
+            Debug.logInfo("[ICalServlet.init] Loading iCalendar Servlet mounted on path " + this.getServletContext().getRealPath("/"), module);
+        }
+    }
+
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        String path = req.getPathInfo();
+        if (UtilValidate.isEmpty(path)) {
+            path = "/";
+        }
+        String workEffortId = path.substring(1);
+        if (workEffortId.contains("/")) {
+            workEffortId = workEffortId.substring(0, workEffortId.indexOf("/"));
+        }
+        if (workEffortId.length() < 1) {
+            resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+            return;
+        }
+        if (Debug.infoOn()) {
+            Debug.logInfo("[ICalServlet.doGet] workEffortId = " + workEffortId, module);
+        }
+        GenericDelegator delegator = null;
+        HttpSession session = req.getSession();
+        String delegatorName = (String) session.getAttribute("delegatorName");
+        if (UtilValidate.isNotEmpty(delegatorName)) {
+            delegator = GenericDelegator.getGenericDelegator(delegatorName);
+        }
+        if (delegator == null) {
+            delegator = (GenericDelegator) this.getServletContext().getAttribute("delegator");
+        }
+        if (delegator == null) {
+            Debug.logError("[ICalServlet.doGet] ERROR: delegator not found in ServletContext", module);
+            resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            return;
+        }
+        Calendar calendar = null;
+        try {
+            calendar = ICalendarWorker.getICalendar(delegator, workEffortId);
+        } catch (Exception e) {
+            Debug.logError("[ICalServlet.doGet] Error while getting iCalendar: " + e, module);
+            resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+            return;
+        }
+        if (calendar == null) {
+            resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
+            return;
+        }
+        resp.setContentType("text/calendar");
+        resp.setStatus(HttpServletResponse.SC_OK);
+        Writer writer = null;
+        if (UtilJ2eeCompat.useOutputStreamNotWriter(this.getServletContext())) {
+            ServletOutputStream ros = resp.getOutputStream();
+            writer = new OutputStreamWriter(ros, "UTF-8");
+        } else {
+            writer = resp.getWriter();
+        }
+        writer.write(calendar.toString());
+        writer.close();
+        if (Debug.infoOn()) {
+            Debug.logInfo("[ICalServlet.doGet] finished request", module);
+        }
+    }
+};

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

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

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

Added: 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=718684&view=auto
==============================================================================
--- ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalendarWorker.java (added)
+++ ofbiz/trunk/applications/workeffort/src/org/ofbiz/workeffort/workeffort/ICalendarWorker.java Tue Nov 18 10:56:11 2008
@@ -0,0 +1,175 @@
+/*******************************************************************************
+ * 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.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+
+import javolution.util.FastList;
+
+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.TimeDuration;
+import org.ofbiz.base.util.UtilMisc;
+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. */
+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,
+            "CAL_CONFIRMED", Status.VEVENT_CONFIRMED, "CAL_CANCELLED", Status.VEVENT_CANCELLED);
+    protected static String workEffortIdPropName = "X-ORG-OFBIZ-WORKEFFORT-ID";
+
+    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"))) {
+            return null;
+        }
+        net.fortuna.ical4j.model.Calendar calendar = makeCalendar(calendarProperties);
+        ComponentList components = calendar.getComponents();
+        List<GenericValue> workEfforts = getRelatedWorkEfforts(calendarProperties);
+        for (GenericValue workEffort : workEfforts) {
+            components.add(makeEvent(workEffort));
+        }
+        return calendar;
+    }
+
+    public static List<GenericValue> getRelatedWorkEfforts(GenericValue workEffort) throws GenericEntityException {
+        GenericDelegator delegator = workEffort.getDelegator();
+        String workEffortId = workEffort.getString("workEffortId");
+        List<GenericValue> relatedParties = EntityUtil.filterByDate(delegator.findList("WorkEffortPartyAssignment", EntityCondition.makeCondition("workEffortId", EntityOperator.EQUALS, workEffortId), null, null, null, false));
+        List<GenericValue> relatedFixedAssets = EntityUtil.filterByDate(delegator.findList("WorkEffortFixedAssetAssign", EntityCondition.makeCondition("workEffortId", EntityOperator.EQUALS, workEffortId), null, null, null, false));
+        List<GenericValue> workEfforts = FastList.newInstance();
+        List<EntityCondition> conditionList = UtilMisc.<EntityCondition>toList(
+                EntityCondition.makeCondition("scopeEnumId", EntityOperator.EQUALS, "WES_PUBLIC"),
+                EntityCondition.makeCondition("workEffortTypeId", EntityOperator.NOT_EQUAL, "PUBLISH_PROPS"));
+        EntityExpr variableExpr = EntityCondition.makeCondition("partyId", EntityOperator.EQUALS, "");
+        conditionList.add(variableExpr);
+        EntityCondition workEffortCond = EntityCondition.makeCondition(conditionList);
+        for (GenericValue partyValue : relatedParties) {
+            variableExpr.init("partyId", EntityOperator.EQUALS, partyValue.get("partyId"));
+            workEfforts.addAll(delegator.findList("WorkEffortAndPartyAssign", workEffortCond, null, null, null, false));
+        }
+        for (GenericValue fixedAssetValue : relatedFixedAssets) {
+            variableExpr.init("fixedAssetId", EntityOperator.EQUALS, fixedAssetValue.get("fixedAssetId"));
+            workEfforts.addAll(delegator.findList("WorkEffortAndPartyAssign", workEffortCond, null, null, null, false));
+        }
+        workEfforts.addAll(EntityUtil.filterByDate(delegator.findList("WorkEffortAssocToView", EntityCondition.makeCondition("workEffortIdFrom", EntityOperator.EQUALS, workEffortId), null, null, null, false)));
+        return WorkEffortWorker.removeDuplicateWorkEfforts(workEfforts);
+    }
+
+    public static VEvent makeEvent(GenericValue workEffort) throws GenericEntityException {
+        GenericDelegator delegator = workEffort.getDelegator();
+        String workEffortId = workEffort.getString("workEffortId");
+        PropertyList eventProps = new PropertyList();
+        eventProps.add(new DtStamp()); // iCalendar object created date/time
+        if (workEffort.getTimestamp("createdDate") != null) {
+            eventProps.add(new Created(new DateTime(workEffort.getTimestamp("createdDate"))));
+        }
+        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 Summary(workEffort.getString("workEffortName")));
+        Status eventStatus = statusMap.get(workEffort.getString("currentStatusId"));
+        if (eventStatus != null) {
+            eventProps.add(statusMap.get(workEffort.getString("currentStatusId")));
+        }
+        Double durationMillis = workEffort.getDouble("estimatedMilliSeconds");
+        if (durationMillis != null) {
+            TimeDuration duration = TimeDuration.fromLong(durationMillis.longValue());
+            eventProps.add(new Duration(new Dur(duration.days(), duration.hours(), duration.minutes(), duration.seconds())));
+        }
+        List<GenericValue> relatedParties = EntityUtil.filterByDate(delegator.findList("WorkEffortPartyAssignView", EntityCondition.makeCondition("workEffortId", EntityOperator.EQUALS, workEffortId), null, null, null, false));
+        for (GenericValue partyValue : relatedParties) {
+            ParameterList paramList = new ParameterList();
+            String partyName = partyValue.getString("groupName");
+            if (UtilValidate.isEmpty(partyName)) {
+                partyName = partyValue.getString("firstName") + " " + partyValue.getString("lastName");
+            }
+            paramList.add(new Cn(partyName));
+            // paramList.add(new XParameter(partyIdPropName, partyValue.getString("partyId")));
+            try {
+                if ("CAL_ORGANIZER~CAL_OWNER".contains(partyValue.getString("roleTypeId"))) {
+                    eventProps.add(new Organizer(paramList, ""));
+                } else {
+                    eventProps.add(new Attendee(paramList, ""));
+                }
+            } catch (Exception e) {}
+        }
+        DateRange range = new DateRange(workEffort.getTimestamp("estimatedStartDate"), workEffort.getTimestamp("estimatedCompletionDate"));
+        eventProps.add(new DtStart(new DateTime(range.start())));
+        if (UtilValidate.isNotEmpty(workEffort.getString("tempExprId"))) {
+            TemporalExpression tempExpr = TemporalExpressionWorker.getTemporalExpression(delegator, workEffort.getString("tempExprId"));
+            if (tempExpr != null) {
+                try {
+                    ICalRecurConverter.convert(tempExpr, eventProps);
+                } catch (Exception e) {
+                    eventProps.add(new Description("Error while converting recurrence: " + e));
+                    eventProps.add(new DtStart());
+                    eventProps.add(new DtEnd());
+                    return new VEvent(eventProps);
+                }
+            }
+        } else {
+            eventProps.add(new DtEnd(new DateTime(range.end())));
+        }
+        if (workEffort.getString("description") != null) {
+            eventProps.add(new Description(workEffort.getString("description")));
+        }
+        return new VEvent(eventProps);
+    }
+
+    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();
+        net.fortuna.ical4j.model.TimeZone timezone = registry.getTimeZone(tz.getID());
+        calendar.getComponents().add(timezone.getVTimeZone());
+        return calendar;
+    }
+}

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

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

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

Added: ofbiz/trunk/applications/workeffort/webapp/ical/WEB-INF/web.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/applications/workeffort/webapp/ical/WEB-INF/web.xml?rev=718684&view=auto
==============================================================================
--- ofbiz/trunk/applications/workeffort/webapp/ical/WEB-INF/web.xml (added)
+++ ofbiz/trunk/applications/workeffort/webapp/ical/WEB-INF/web.xml Tue Nov 18 10:56:11 2008
@@ -0,0 +1,98 @@
+<?xml version="1.0"?>
+<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
+
+<!--
+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.
+-->
+
+<web-app>
+  <display-name>Open For Business - iCalendar Server</display-name>
+  <description>iCalendar Server Module of the Open For Business Project</description>
+
+  <context-param>
+    <param-name>entityDelegatorName</param-name>
+    <param-value>default</param-value>
+    <description>The Name of the Entity Delegator to use, defined in entityengine.xml</description>
+  </context-param>
+  <context-param>
+    <param-name>localDispatcherName</param-name>
+    <param-value>ical</param-value>
+    <description>A unique name used to identify/recognize the local dispatcher for the Service Engine</description>
+  </context-param>
+  <context-param>
+    <param-name>serviceReaderUrls</param-name>
+    <param-value>/WEB-INF/services.xml</param-value>
+    <description>Configuration File(s) For The Service Dispatcher</description>
+  </context-param>
+  <context-param>
+    <param-name>scriptLocationPath</param-name>
+    <param-value>/WEB-INF/bsh</param-value>
+    <description>BeanShell Script Location</description>
+  </context-param>    
+    
+    <filter>
+        <filter-name>ContextFilter</filter-name>
+        <display-name>ContextFilter</display-name>
+        <filter-class>org.ofbiz.webapp.control.ContextFilter</filter-class>
+        <init-param>
+            <param-name>disableContextSecurity</param-name>
+            <param-value>N</param-value>
+        </init-param>
+        <init-param>
+            <param-name>allowedPaths</param-name>
+            <param-value>/control:/select:/index.html:/index.jsp:/default.html:/default.jsp:/images:/includes/maincss.css</param-value>
+        </init-param>
+        <init-param>
+            <param-name>errorCode</param-name>
+            <param-value>403</param-value>
+        </init-param>
+        <init-param>
+            <param-name>redirectPath</param-name>
+            <param-value>/control/main</param-value>
+        </init-param>        
+    </filter>
+    <filter-mapping>
+        <filter-name>ContextFilter</filter-name>
+            <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+  <listener><listener-class>org.ofbiz.webapp.control.ControlEventListener</listener-class></listener>
+  <listener><listener-class>org.ofbiz.webapp.control.LoginEventListener</listener-class></listener>
+  <!-- NOTE: not all app servers support mounting implementations of the HttpSessionActivationListener interface -->
+  <!-- <listener><listener-class>org.ofbiz.webapp.control.ControlActivationEventListener</listener-class></listener> -->
+  
+  <servlet>
+    <servlet-name>iCalendarServlet</servlet-name>
+    <display-name>iCalendarServlet</display-name>
+    <description>Main Control Servlet</description>
+    <servlet-class>org.ofbiz.workeffort.workeffort.ICalServlet</servlet-class>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+  <servlet-mapping>
+    <servlet-name>iCalendarServlet</servlet-name>
+    <url-pattern>/*</url-pattern>
+  </servlet-mapping>
+
+  <session-config>
+    <session-timeout>60</session-timeout>    <!-- in minutes -->
+  </session-config>
+
+  <welcome-file-list>
+    <welcome-file>index.jsp</welcome-file>
+  </welcome-file-list>
+</web-app>

Propchange: ofbiz/trunk/applications/workeffort/webapp/ical/WEB-INF/web.xml
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/trunk/applications/workeffort/webapp/ical/WEB-INF/web.xml
------------------------------------------------------------------------------
    svn:keywords = "Date Rev Author URL Id"

Propchange: ofbiz/trunk/applications/workeffort/webapp/ical/WEB-INF/web.xml
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: ofbiz/trunk/framework/base/lib/ical4j-1.0-beta5.jar
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/base/lib/ical4j-1.0-beta5.jar?rev=718684&view=auto
==============================================================================
Binary file - no diff available.

Propchange: ofbiz/trunk/framework/base/lib/ical4j-1.0-beta5.jar
------------------------------------------------------------------------------
    svn:mime-type = application/octet-stream

Modified: ofbiz/trunk/specialpurpose/projectmgr/data/ProjectMgrDemoData.xml
URL: http://svn.apache.org/viewvc/ofbiz/trunk/specialpurpose/projectmgr/data/ProjectMgrDemoData.xml?rev=718684&r1=718683&r2=718684&view=diff
==============================================================================
--- ofbiz/trunk/specialpurpose/projectmgr/data/ProjectMgrDemoData.xml (original)
+++ ofbiz/trunk/specialpurpose/projectmgr/data/ProjectMgrDemoData.xml Tue Nov 18 10:56:11 2008
@@ -158,4 +158,8 @@
     <WorkEffortPartyAssignment workEffortId="STAFF_MTG" partyId="DemoEmployee2" statusId="PRTYASGN_ASSIGNED" roleTypeId="CAL_ATTENDEE" availabilityStatusId="WEPA_AV_BUSY" fromDate="2008-01-01 00:00:00.0"/>
     <WorkEffortPartyAssignment workEffortId="STAFF_MTG" partyId="DemoEmployee3" statusId="PRTYASGN_ASSIGNED" roleTypeId="CAL_ATTENDEE" availabilityStatusId="WEPA_AV_BUSY" fromDate="2008-01-01 00:00:00.0"/>
 
+    <!-- Publish the staff meeting calendar event -->
+    <WorkEffort workEffortId="CALENDAR_PUB_DEMO" workEffortTypeId="PUBLISH_PROPS" currentStatusId="CAL_CANCELLED" scopeEnumId="WES_PUBLIC" description="Demo Project 1 Customer 1" workEffortName="iCalendar Publish Demonstration"/>
+    <WorkEffortAssoc workEffortIdFrom="CALENDAR_PUB_DEMO" workEffortIdTo="STAFF_MTG" workEffortAssocTypeId="WORK_EFF_DEPENDENCY" fromDate="2008-01-01 00:00:00.0"/>
+
 </entity-engine-xml>