You are viewing a plain text version of this content. The canonical link for it is here.
Posted to derby-commits@db.apache.org by da...@apache.org on 2006/10/07 06:59:29 UTC

svn commit: r453845 [1/2] - in /db/derby/code/trunk/java/demo/localcal: ./ lib/ src/ src/images/

Author: davidvc
Date: Fri Oct  6 21:59:28 2006
New Revision: 453845

URL: http://svn.apache.org/viewvc?view=rev&rev=453845
Log:
DERBY-1936 - LocalCalendar Sample Application

Added:
    db/derby/code/trunk/java/demo/localcal/
    db/derby/code/trunk/java/demo/localcal/README   (with props)
    db/derby/code/trunk/java/demo/localcal/build.xml   (with props)
    db/derby/code/trunk/java/demo/localcal/lib/
    db/derby/code/trunk/java/demo/localcal/src/
    db/derby/code/trunk/java/demo/localcal/src/AddEventRequest.java   (with props)
    db/derby/code/trunk/java/demo/localcal/src/CalEvent.java   (with props)
    db/derby/code/trunk/java/demo/localcal/src/CalendarController.java   (with props)
    db/derby/code/trunk/java/demo/localcal/src/DatabaseManager.java   (with props)
    db/derby/code/trunk/java/demo/localcal/src/DeleteEventRequest.java   (with props)
    db/derby/code/trunk/java/demo/localcal/src/EventManager.java   (with props)
    db/derby/code/trunk/java/demo/localcal/src/GCalendar.java   (with props)
    db/derby/code/trunk/java/demo/localcal/src/GCalendarRequest.java   (with props)
    db/derby/code/trunk/java/demo/localcal/src/NetworkDownException.java   (with props)
    db/derby/code/trunk/java/demo/localcal/src/RequestManager.java   (with props)
    db/derby/code/trunk/java/demo/localcal/src/UpdateEventRequest.java   (with props)
    db/derby/code/trunk/java/demo/localcal/src/images/
    db/derby/code/trunk/java/demo/localcal/src/images/delete.gif   (with props)
    db/derby/code/trunk/java/demo/localcal/src/index.html   (with props)
    db/derby/code/trunk/java/demo/localcal/src/localcal.js   (with props)

Added: db/derby/code/trunk/java/demo/localcal/README
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/demo/localcal/README?view=auto&rev=453845
==============================================================================
--- db/derby/code/trunk/java/demo/localcal/README (added)
+++ db/derby/code/trunk/java/demo/localcal/README Fri Oct  6 21:59:28 2006
@@ -0,0 +1,269 @@
+LocalCalendar is an example application which allows you to manage
+Google Calendar entries while disconnected from the network and
+then synchronize those changes back to Google Calendar when you
+get back online.
+
+The intention of this application is to provide code that demonstrates
+some of the key aspects of an offline architecture, including use
+of Apache Derby for local storage, the value of a transactional
+system, synchronization and conflict resolution, and crash recovery.
+
+
+1. WHAT YOU WILL NEED 
+
+1.1 MOZILLA FIREFOX 1.5
+  http://www.mozilla.com/firefox/
+  NOTE: I have *not* tested this on IE or other browsers, and I am
+    pretty sure it doesn't work.  You need Firefox to run the application
+    in this tutorial.
+
+1.2 JAVA DEVELOPMENT KIT 1.5 (or greater)
+  http://java.sun.com/javase/downloads/index.jsp
+
+1.3 SUN JAVA PLUGIN 1.5 (or greater) FOR FIREFOX
+  The version of the plugin should be no less than the version of
+  your JDK
+
+  If you don't already have this installed, I am pretty sure when you
+  install the JDK this gets set up for you automatically.  To test
+  this go to the location "about:plugins" in your Firefox browser
+  window.  It should list the Java 1.5 plugin.
+
+  To be doubly sure, go to http://java.com and click on
+  "Verify Installation"
+
+  If it does *not* appear to be installed, then for Windows you can
+  go to
+    http://java.com, and click on Download Now
+
+  For other platforms, go to
+    http://www.java.com/en/download/manual.jsp
+
+  and choose the appropriate platform and follow instructions for
+  install and verification.
+
+
+1.4 APACHE ANT 1.6 or greater
+  http://ant.apache.org
+
+1.5 APACHE DERBY 10.1.3 or greater
+  http://db.apache.org/derby/derby_downloads.html
+
+  All you need is derby.jar.  Place a copy of derby.jar in the lib directory.
+
+1.6 A GOOGLE CALENDAR ACCOUNT
+  You need to get an account at http://calendar.google.com.  The
+  email address and password you use to sign up are what you use
+  to log on to LocalCalendar.
+
+1.7 GOOGLE CALENDAR DATA API JAVA CLIENT
+  http://code.google.com/apis/gdata/download/gdata.java.zip
+
+  Extract the jar files you find inside this zip file and place them
+  in the lib directory.
+
+  for more info on this API, see
+    http://code.google.com/apis/gdata/calendar.html
+
+1.8 JSON FOR JAVA
+  This is used to map data sets between Java and JavaScript.
+
+  Download http://www.json.org/java/json.zip.  This is a source
+  zip file.  Extract this into the src directory.  It will be built
+  as part of the overall build of LocalCalendar and added to localcal.jar
+
+
+
+2. SIGNING THE JAR FILES
+
+LocalCalendar runs as an applet, but the code will be performing
+operations that normally aren't allowed inside the applet sandbox.
+For this reason you need to create a key and then use this key
+to sign the jar files.
+
+2.1 Creating the key
+Using the keytool utility that is part of the JDK, run the following
+command:
+
+  keytool -genkey -alias <your-alias>
+
+and follow the prompts.  <your-alias> can be whatever alias you want
+to use, such as your username.
+
+2.2 Signing the jar files
+
+Using the jarsigner utility that is part of the JDK, run the following
+commands
+
+  jarsigner lib/derby.jar <alias>
+  jarsigner lib/gdata-calendar-1.0.jar <alias>
+  jarsigner lib/gdata-client-1.0.jar <alias>
+
+
+3. BUILDING
+
+3.1 SET KEYSTORE PROPERTIES IN BUILD.XML
+Edit build.xml and set the keystore.alias and keystore.password
+values to match the ones you used above when signing the jar files.
+This is used to automatically sign the LocalCalendar jar file each
+time we build it.
+
+3.2 SET THE CALENDAR ID
+
+The calendar id uniquely identifies the calendar you want
+to work with.  Currently it is set to a test calendar that is
+owned by David Van Couvering.  You are free to use this, it is
+available to all, but you can also create your own calendar.  Here
+are the steps:
+
+3.2.1 Create a calendar
+Make sure it is set to be publicly available to all.  If you want
+to make it private, read the instructions for Google Calendar and
+the Google Calendar API for how to do this.
+
+3.2.2 Get the id
+On the left hand pane of your Google Calendar page is the list of
+calendars you have created.  Click on the down-arrow for the
+one you want to use in LocalCalendar and choose "Calendar Settings"
+
+On the settings page, click on the orange [XML] box for calendar
+address.  You'll get a long URI of the form
+
+http://www.google.com/calendar/feeds/<id>@group.calendar.google.com/public/basic
+
+(unless you're using your default calendar, in which case it will be
+of the form
+
+http://www.google.com/calendar/feeds/<your-email-address>/public/basic)
+
+Your calendar id is either <id>@group.calendar.google.com or
+<your-email-address>.  
+
+3.2.3 Set the id in LocalCalendar
+Set the variable derbycal.calid in src/localcal.js to be your
+calendar id.
+
+3.3 SET THE DATE RANGE IN LOCALCALENDAR
+The date range used by LocalCalendar is currently hardcoded.  If you're
+interested, please feel free to make this dynamically settable by the
+user.  But I didn't get around to doing that.
+
+To change the date range, edit src/localcal.js and modify the
+array derbycal.days to match the dates you're interested in.
+
+3.4 CHANGE THE TITLE
+If you want, you can change the title to match what your calendar
+is about.  Edit src/index.html and change the <h1> tag with the
+id "loginHeader" and also the <h1> tag just underneat the <div>
+with id "main-div".
+
+3.4 BUILD
+To build, simply run "ant" in the top-level directory.  This will
+build the source, create and sign a jar file, and put this as well
+as all other needed resources into the dist directory.
+
+
+4 RUNNING LOCALCALENDAR
+
+Bring up Firefox and point it to <path-to-localcal>/dist/index.html.
+
+LocalCalendar shows a week's worth of events.  Each event is an 
+all-day event.  I didn't try to mess around with event times, this
+was more HTML and JavaScript than I was willing to write.
+
+To play around with LocalCalendar's capabilities, try the following.
+To find bugs, try almost anything else :)
+
+4.1 BASIC ONLINE OPERATION
+
+- Start LocalCalendar
+
+- Log in to Google Calendar using your id/password
+Notice that the app turns green and shows the events from your
+calendar once you're online.  Green means online.  Blue means
+offline.  The power of DHTML.
+
+- If you view the Java Console, it will tell you where output
+  is being logged.  To watch this go by as you're working, do
+  a running tail of the log file (e.g. tail -f <logfile>).  The
+  default location is <java user.dir>/localcal.log
+
+- Add an event by choosing a day, entering a title, and clicking 
+  the [Add Event] button
+
+- Delete an event by clicking the red X next to it
+
+- Update an event by selecting it and modifying it.  When you exit
+  the field it is updated automatically
+
+- In a separate tab or window log on to Google Calendar.  Notice your
+  modifications are reflected in Google Calendar.
+
+
+4.2 OFFLINE OPERATION
+
+- Unplug your network or disable your network
+
+- Try to access Google Calendar and notice that it's not available.
+  Bummer.
+
+- Make a change to LocalCalendar.  LocalCalendar automatically detects
+  the network is down and goes offline.  The change is still stored
+  locally and is reflected in your application.
+
+- Make a couple more changes
+
+- Re-enable your network
+
+- Try Google Calendar, and notice it's available again (whew!)
+
+- In LocalCalendar, click [Go Online].  LocalCalendar synchronizes
+  its changes with Google Calendar, and then turns green.  
+
+- Refresh Google Calendar and notice your changes in LocalCalendar
+  are now reflected (yay!)
+
+
+4.2 CRASH RECOVERY
+This demonstrates the power of Derby's automatic crash recovery
+and consistent durability
+
+- Click [Go Offline] 
+
+- Make some offline changes
+
+- Kill Firefox (yipes!)
+
+- Restart Firefox and open Local Calendar.  Log in to Google
+  Calendar.
+
+- Notice that LocalCalendar automatically detects that you have
+  made changes and synchronizes with Google Calendar after you 
+  log in.
+
+4.3 CONFLICT DETECTION
+Google Calendar has conflict detection.  When you get an entry
+from Google Calendar, it includes a version id, and you send that
+version id when you update.  If the version ids don't match, Google
+Calendar will reject the update.
+
+- Click [Go Offline]
+
+- Make an update to an existing entry
+
+- Go to Google Calendar
+
+- Update the same entry
+
+- Click [Go Online]
+
+- You should get a popup saying there was a conflict during
+  synchronization
+
+- Look at the log file and you'll see the detailed error
+  information
+
+
+ARCHITECTURE
+
+<Coming Soon>

Propchange: db/derby/code/trunk/java/demo/localcal/README
------------------------------------------------------------------------------
    svn:eol-style = native

Added: db/derby/code/trunk/java/demo/localcal/build.xml
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/demo/localcal/build.xml?view=auto&rev=453845
==============================================================================
--- db/derby/code/trunk/java/demo/localcal/build.xml (added)
+++ db/derby/code/trunk/java/demo/localcal/build.xml Fri Oct  6 21:59:28 2006
@@ -0,0 +1,71 @@
+<?xml version="1.0"?>
+
+<!--
+  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.
+-->
+
+<project default="all" basedir=".">
+  <property name="classes.dir" value="classes"/>
+  <property name="src.dir" value="src"/>
+  <property name="dist.dir" value="dist"/>
+  <property name="lib.dir" value="lib"/>
+  
+  <!-- CHANGE THIS TO YOUR ALIAS NAME -->
+  <property name="keystore.alias" value="davidvc"/>
+  <!-- CHANGE THIS TO YOUR PASSWORD -->
+  <property name="keystore.password" value="secret"/>
+
+  <target name="all" depends="compile, jar, dist"/>
+  
+  <target name="clean">
+    <delete dir="${classes.dir}"/>
+    <mkdir dir="${classes.dir}"/>
+    <delete dir="${dist.dir}"/>
+    <mkdir dir="${dist.dir}"/>
+  </target>
+  
+  <target name="compile">
+    <mkdir dir="${classes.dir}"/>
+    <javac
+      srcdir="${src.dir}"
+      destdir="${classes.dir}">
+      <classpath>
+        <pathelement path="${classes.dir}"/>
+        <pathelement path="${lib.dir}/derby.jar"/>
+        <pathelement path="${lib.dir}/gdata-calendar-1.0.jar"/>
+        <pathelement path="${lib.dir}/gdata-client-1.0.jar"/>
+      </classpath>
+    </javac>
+  </target>
+  
+  <target name="jar">
+    <jar basedir="${classes.dir}" destfile="${lib.dir}/localcal.jar"/>
+    <signjar jar="${lib.dir}/localcal.jar" alias="${keystore.alias}"
+      storepass="${keystore.password}"/>
+  </target>
+
+  <target name="dist">
+    <mkdir dir="${dist.dir}"/>
+    <copy todir="${dist.dir}">
+      <fileset dir="${lib.dir}"/>
+    </copy>
+    <copy file="${src.dir}/index.html" todir="${dist.dir}"/>
+    <copy file="${src.dir}/localcal.js" todir="${dist.dir}"/>
+    <copy todir="${dist.dir}/images">
+      <fileset dir="${src.dir}/images"/>
+    </copy>
+  </target>
+</project>

Propchange: db/derby/code/trunk/java/demo/localcal/build.xml
------------------------------------------------------------------------------
    svn:eol-style = native

Added: db/derby/code/trunk/java/demo/localcal/src/AddEventRequest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/demo/localcal/src/AddEventRequest.java?view=auto&rev=453845
==============================================================================
--- db/derby/code/trunk/java/demo/localcal/src/AddEventRequest.java (added)
+++ db/derby/code/trunk/java/demo/localcal/src/AddEventRequest.java Fri Oct  6 21:59:28 2006
@@ -0,0 +1,76 @@
+/*
+ 
+   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.
+ 
+ */
+
+import java.sql.*;
+
+/**
+ * Encapsulates a request to add an event
+ */
+public class AddEventRequest extends GCalendarRequest {
+    
+    private String eventId;
+    private String date;
+    private String title;
+            
+    /** Creates a new instance of AddEventRequest */
+    protected AddEventRequest(int requestId, String eventId, String date, 
+            String title) {
+        super(requestId);
+        setEventId(eventId);
+        setDate(date);
+        setTitle(title);
+    }
+        
+    public String toString() {
+        return "Request # " + getId() + " to add event (" + getDate() + ": " +
+                getTitle() + ")";
+    }
+    
+    public void submit(GCalendar calendar) throws Exception {
+        CalEvent event = calendar.addEvent(getDate(), getTitle());
+        
+        // Fix the database so that the id returned by Google Calendar
+        // is used.
+        EventManager.updateEventId(eventId, event.getId());
+    }
+    
+    public String getDate() {
+        return date;
+    }
+
+    public void setDate(String date) {
+        this.date = date;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getEventId() {
+        return eventId;
+    }
+
+    public void setEventId(String eventId) {
+        this.eventId = eventId;
+    }
+}

Propchange: db/derby/code/trunk/java/demo/localcal/src/AddEventRequest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: db/derby/code/trunk/java/demo/localcal/src/CalEvent.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/demo/localcal/src/CalEvent.java?view=auto&rev=453845
==============================================================================
--- db/derby/code/trunk/java/demo/localcal/src/CalEvent.java (added)
+++ db/derby/code/trunk/java/demo/localcal/src/CalEvent.java Fri Oct  6 21:59:28 2006
@@ -0,0 +1,124 @@
+/*
+ 
+   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.
+ 
+ */
+
+import java.util.Date;
+import java.text.SimpleDateFormat;
+import org.json.*;
+
+/**
+ * Encapsulates an event
+ */
+public class CalEvent implements java.io.Serializable {
+    private String id;
+    private String date;
+    private String title;
+    private String editURL;
+    private String versionId;
+    
+    private SimpleDateFormat dayFormat = new SimpleDateFormat("EEEE");
+    
+    private SimpleDateFormat fullFormat = new SimpleDateFormat("yyyy-MM-dd");
+    
+    /** Creates a new instance of DerbyCalEvent 
+     *  
+     *  @param id
+     *      The id for this event.  This may be a temporary id until we're
+     *      assigned an "official" one by Google Calendar (whenever this event
+     *      is posted to Google Calendar).
+     * 
+     *  @param date
+     *      The day for this event, in the format <yyyy>-<mm>-<dd>
+     * 
+     *  @param title
+     *      The title for the event
+     *
+     *  @param editURL
+     *      The edit URL provided by Google Calendar (can be null)
+     *
+     *  @param versionId
+     *      The version identifier provided by Google Calendar (can be null)
+     */
+    public CalEvent(String id, String date, String title,
+            String editURL, String versionId) {
+        this.id         = id;
+        this.date       = date;
+        this.title      = title;
+        this.editURL    = editURL;
+        this.versionId  = versionId;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+    
+    public String getId() {
+        return id;
+    }
+
+    public String getDate() {
+        return date;
+    }
+    
+    /** Get the day string for the date */
+    public String getDay() throws Exception {
+        return dayFormat.format(fullFormat.parse(getDate()));
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public void setDate(String date) {
+        this.date = date;
+    }
+    
+    /** 
+     * Get the JSONObject for this event
+     */
+    public JSONObject getJSONObject() throws Exception {
+        JSONObject jobj = new JSONObject();
+        jobj.put("eventid", getId());
+        jobj.put("day", getDay());
+        jobj.put("title", getTitle());
+        jobj.put("date", getDate());
+        jobj.put("editURL", getEditURL());
+        jobj.put("versionId", getVersionId());
+        
+        return jobj;
+    }
+
+    public String getEditURL() {
+        return editURL;
+    }
+
+    public String getVersionId() {
+        return versionId;
+    }
+        
+    public String toString() {
+        return "id: " + getId() +", date: " + getDate() + ", title: " +
+                getTitle();
+    }
+
+        
+}

Propchange: db/derby/code/trunk/java/demo/localcal/src/CalEvent.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: db/derby/code/trunk/java/demo/localcal/src/CalendarController.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/demo/localcal/src/CalendarController.java?view=auto&rev=453845
==============================================================================
--- db/derby/code/trunk/java/demo/localcal/src/CalendarController.java (added)
+++ db/derby/code/trunk/java/demo/localcal/src/CalendarController.java Fri Oct  6 21:59:28 2006
@@ -0,0 +1,409 @@
+/*
+
+   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.
+
+*/
+import com.sun.media.sound.JavaSoundAudioClip;
+import java.applet.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.awt.*;
+import java.io.*;
+import java.security.*;
+
+
+import org.json.*;
+
+/**
+ * This is the controller for the local calendar application
+ */
+public class CalendarController extends Applet {
+    public static final String DBNAME = "LocalCalDB";
+    private String calid;
+    private String gmtOffset;
+    private String user;
+    private String password;
+    private String startDay;
+    private String endDay;
+    
+    private static PrintStream console;
+    private static final String CONSOLE_FILENAME = "localcal.log";
+    
+    /** The calendar we're logged in to */
+    GCalendar   calendar;
+    
+    /** Indicates whether we're online or not */
+    boolean     online = true;
+        
+    /**
+     * Log in to the Google Calendar service.  
+     *
+     * @param calid
+     *      The calendar id.  If this is your default Google Calendar,
+     *      it's the email address you use to login to Google Calendar,
+     *      e.g. "david.vancouvering@gmail.com".
+     *      <p>
+     *      If it's not your default calendar, you can get the calendar id
+     *      for the calendar you want by doing the following:
+     *      <ul>
+     *      <li>Go to your Google Calendar page
+     *      <li>On the left pane all your calendars are listed.  Click on the
+     *          drop-down menu for the calendar you want, and choose 
+     *          "Calendar settings".
+     *      <li>Under "Calendar Address" click on the [XML] button.  A window
+     *          will pop up that will give you a URL of the form <pre>
+     *          "http://www.google.com/calendar/feeds/<token>@group.calendar.google.com/public/basic"
+     *          </pre>
+     *      <li>Your calendar id is "<token>@group.calendar.google.com"
+     *      </ul>
+     *
+     *  @param gmtOffset
+     *      The offset, positive or negative, from GMT for the time zone for
+     *      the calendar
+     *
+     * @param user
+     *      your username for your Google Calendar account, e.g.
+     *      david.vancouvering@gmail.com
+     *
+     * @param password
+     *      your password for your Google Calendar account
+     *
+     * @param startDay
+     *      The starting day for this calendar in the format
+     *      <yyyy>-<mm>-<dd>
+     *
+     * @param endDay
+     *      The ending day inclusive for this calendar, in the format
+     *      <yyyy>-<mm>-<dd>
+     *
+     * @return a JSON Array containing the list of events from Google
+     */
+    public void login(String calid, String gmtOffset, 
+            String user, String password, String startDay,
+            String endDay, boolean drop) throws Exception {
+        // Create the calendar, and if the login succeeds, start up the thread
+        log("DerbyCalendarApplet.login(" + calid + ", " + gmtOffset + ", " +
+                user + ", " + startDay + ", " + endDay + ")");
+        
+        this.calid      = calid;
+        this.gmtOffset  = gmtOffset;
+        this.user       = user;
+        this.password   = password;
+        this.startDay   = startDay;
+        this.endDay     = endDay;
+                        
+        goOnline();
+    }
+    
+    private void startConsole(String dir) throws Exception {
+        final String path = dir + "/" + CONSOLE_FILENAME;
+        AccessController.doPrivileged(
+            new PrivilegedExceptionAction<Object>() {
+                public Object run() throws Exception {
+                    log("Writing log to " + path);
+                    console = new PrintStream(
+                        new FileOutputStream(path));
+                    System.setOut(console);
+                    System.setErr(console);
+                    return null;
+                }
+            }
+        );
+    }
+    
+    /**
+     * Go online.  This means (a) send up to Google Calendar any stored
+     * requests, logging any errors that occur and (b) getting the latest
+     * list of events from Google Calendar.
+     *
+     * @return a JSON String representing the latest list of events for
+     *      this calendar
+     */
+    public void goOnline() throws Exception {
+        log("GOING ONLINE...");
+        this.online = true;
+
+        try {
+            // Log in to Google Calendar  
+            calendar = new GCalendar(calid, gmtOffset, user, password, 
+                    startDay, endDay);
+            
+            RequestManager.submitRequests(calendar);
+        } catch ( Exception e ) {
+            e.printStackTrace();
+            throw e;
+        }        
+    }
+
+    public void goOffline() {
+        log("GOING OFFLINE");
+        this.online = false;
+    }
+    
+    public boolean isOnline() {
+        return this.online;
+    }
+    
+        /**
+     * Refresh our calendar from Google Calendar and return a JSON string that 
+     * represents the array of entries for the given date range
+     *
+     * @return a JSON string that represents an array of all the entries
+     *      for the calendar.
+     */
+    public String refresh() throws Exception {
+        log("DerbyCalendarApplet.refresh()");
+        
+
+        Collection<CalEvent> events = null;
+        if ( isOnline() ) {
+            try {
+                events = calendar.getEvents();
+
+                // Refresh the database with the events we got from Google
+                // Calendar
+                EventManager.refresh(events);
+            } catch ( NetworkDownException nde ) {
+                log("The network is down, going offline");
+                goOffline();
+            }
+        }
+        
+        if ( ! isOnline() ) {
+            events = EventManager.getEvents();
+        }
+
+        JSONArray jarray = new JSONArray();
+        for ( CalEvent event : events ) {
+            
+            jarray.put(event.getJSONObject());
+        }
+                
+        return jarray.toString();
+    }        
+    
+    // Return a list of conflicts as a string so it can be reported
+    // as an error
+    public String getConflicts() {
+        java.util.List<String> conflicts = RequestManager.getConflicts();
+        if ( conflicts.size() == 1 ) {
+            return "There was 1 error during synchronization.  Please " +
+                "see the error log for details.";
+        } else if ( conflicts.size() > 1 ) {
+            return "There were " + conflicts.size() + " errors during " +
+                "synchronization.  Please see the error log for details.";            
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Add an entry to the calendar
+     *
+     * @param id 
+     *      The unique identifier for this new entry
+     *
+     * @param date
+     *      The date for this entry, in the form of <yyyy>-<mm>-<dd>
+     * 
+     * @param title
+     *      The title for the entry
+     *
+     * @return the new id returned by Google Calendar
+     */
+    public String addEvent(String id, String date, String title) 
+            throws Exception {
+        log("DerbyCalendarApplet.addEntry(" + id  + 
+                ", " + date + ", " + title + ")");
+        
+        CalEvent event = null;
+        
+        if ( isOnline() ) {
+            try {
+                event = calendar.addEvent(date, title);
+            } catch ( NetworkDownException nde ) {
+                log("The network is down, going offline");
+                goOffline();
+            }
+        }
+        
+        // Now do the database operations -- store the event
+        // locally, and if we're offline, also store the request
+        // to add the event so we can ship it to Google Calendar 
+        // when we come back online
+        try {
+            DatabaseManager.beginTransaction();
+            
+            if ( ! isOnline() ) {
+                log("Storing request to add event");
+                RequestManager.storeAddEvent(id, date, title);
+                event = new CalEvent(id, date, title, null, null);
+            }   
+            
+            log("Storing new event in the local database");            
+            EventManager.addEvent(event);
+            
+            DatabaseManager.commitTransaction();
+        } catch ( Exception e ) {
+            DatabaseManager.rollbackTransaction();
+            throw e;
+        }
+
+        return event.getJSONObject().toString();
+}
+    
+    public void updateEvent(String id, String title) throws Exception {
+        log("DerbyCalendarApplet.updateEntry(" + id  + ", " + title + ")");
+        CalEvent event = EventManager.getEvent(id);
+        event.setTitle(title);
+
+        if ( isOnline() ) {
+            try {
+                event = calendar.updateEvent(event);
+            } catch ( NetworkDownException nde ) {
+                log("The network is down, going offline");
+                goOffline();
+            }
+        }
+
+        // Now do the database operations -- store the event
+        // locally, and if we're offline, also store the request
+        // to add the event so we can ship it to Google Calendar 
+        // when we come back online
+        try {
+            DatabaseManager.beginTransaction();
+            
+            if ( ! isOnline() ) {
+                log("Storing request to update event");
+                RequestManager.storeUpdateEvent(event);
+            }   
+            
+            log("Updating event in the local database");            
+            EventManager.updateEvent(event);
+            
+            DatabaseManager.commitTransaction();
+        } catch ( Exception e ) {
+            DatabaseManager.rollbackTransaction();
+            throw e;
+        }
+
+    }
+    
+    public void deleteEvent(String id) throws Exception {
+        log("DerbyCalendarApplet.deleteEntry(" + id  + ")");
+        CalEvent event = EventManager.getEvent(id);
+        
+        if ( isOnline() ) {
+            try {
+                if ( event == null ) {
+                    throw new Exception("Can't find even in the database: " +
+                            id);
+                }
+                
+                calendar.deleteEvent(event.getEditURL());
+            } catch ( NetworkDownException nde ) {
+                log("The network is down, going offline");
+                goOffline();
+            }
+        }
+
+        // Now do the database operations -- store the event
+        // locally, and if we're offline, also store the request
+        // to add the event so we can ship it to Google Calendar 
+        // when we come back online
+        try {
+            DatabaseManager.beginTransaction();
+            
+            if ( ! isOnline() ) {
+                log("Storing request to delete event");
+                RequestManager.storeDeleteEvent(id, event.getEditURL());
+            }   
+            
+            log("Deleting event in the local database");            
+            EventManager.deleteEvent(id);
+            
+            DatabaseManager.commitTransaction();
+        } catch ( Exception e ) {
+            DatabaseManager.rollbackTransaction();
+            throw e;
+        }
+
+    }
+    
+    /** 
+     * Empty out the calendar.  Used mostly for testing 
+     */
+    public void clearCalendar() throws Exception {
+        DatabaseManager.clearTables();
+        calendar.clearCalendar();
+    }
+    
+    private void log(String str) {
+        System.out.println(str);
+    }
+        
+    /**
+     * Call this to turn on logging of SQL to derby.log,
+     * for debuggig
+     */
+    public void logSql() throws Exception {
+        DatabaseManager.logSql();
+    }
+    
+    public void init() {
+        log("Applet init, applet is " + this.hashCode());
+        try {
+            AccessController.doPrivileged(
+                new PrivilegedExceptionAction<Object>() {
+                    public Object run() throws Exception {
+                        String userdir = System.getProperty("user.home");
+                        String dbname = userdir + "/" + DBNAME;
+                        
+                        startConsole(userdir);
+                        
+                        DatabaseManager.initDatabase(dbname, 
+                            "user", "secret", false); 
+                        log("Database initialized, " +
+                            "database directory is " + dbname); 
+                        
+                        return null;
+                    }
+                }
+            );
+        } catch ( PrivilegedActionException e ) {
+            e.getException().printStackTrace();
+        }         
+    }
+    
+    public void start() {
+        log("Applet start, applet is " + this.hashCode());
+    }
+       
+    public void stop() {
+        log("Applet stop, applet is " + this.hashCode());
+    }
+    
+    public void destroy() {
+        log("Applet destroy, applet is " + this.hashCode());
+    }
+    
+    /** Still need to figure this one out...
+    public void paint(Graphics g) {
+        g.drawString("Repainting", 50, 25);
+        g.drawString(consoleStream.toString(), 50, 35);
+    }
+     */
+}

Propchange: db/derby/code/trunk/java/demo/localcal/src/CalendarController.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: db/derby/code/trunk/java/demo/localcal/src/DatabaseManager.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/demo/localcal/src/DatabaseManager.java?view=auto&rev=453845
==============================================================================
--- db/derby/code/trunk/java/demo/localcal/src/DatabaseManager.java (added)
+++ db/derby/code/trunk/java/demo/localcal/src/DatabaseManager.java Fri Oct  6 21:59:28 2006
@@ -0,0 +1,221 @@
+/*
+ 
+   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.
+ 
+ */
+
+import javax.sql.*;
+import java.sql.*;
+import org.apache.derby.jdbc.EmbeddedDataSource;
+
+/**
+ * A container for the singleton data source, so we don't have to
+ * create a separate one for each class that wants to do JDBC
+ */
+public class DatabaseManager {
+    
+    private static EmbeddedDataSource ds;
+    
+    public static String REQUESTS_TABLE = "APP.REQUESTS";
+    public static String EVENTS_TABLE   = "APP.EVENTS";
+    
+    // We want to keep the same connection for a given thread
+    // as long as we're in the same transaction
+    private static ThreadLocal<Connection> tranConnection = new ThreadLocal();
+
+    private static void initDataSource(String dbname, String user, 
+            String password) {
+        ds = new EmbeddedDataSource();
+        ds.setDatabaseName(dbname);
+        ds.setUser(user);
+        ds.setPassword(password);
+        ds.setCreateDatabase("create");   
+    }
+    
+    public static void logSql() throws Exception {
+        executeUpdate("CALL SYSCS_UTIL.SYSCS_SET_DATABASE_PROPERTY(" +
+            "'derby.language.logStatementText', 'true')");
+    }
+    
+    public static synchronized void beginTransaction() throws Exception {
+        if ( tranConnection.get() != null ) {
+            throw new Exception("This thread is already in a transaction");
+        }
+        Connection conn = getConnection();
+        conn.setAutoCommit(false);
+        tranConnection.set(conn);
+    }
+    
+    public static void commitTransaction() throws Exception {
+        if ( tranConnection.get() == null ) {
+            throw new Exception("Can't commit: this thread isn't currently in a " +
+                    "transaction");
+        }
+        tranConnection.get().commit();
+        tranConnection.set(null);
+    }
+    
+    public static void rollbackTransaction() throws Exception {
+        if ( tranConnection.get() == null ) {
+            throw new Exception("Can't rollback: this thread isn't currently in a " +
+                    "transaction");
+        }
+        tranConnection.get().rollback();
+        tranConnection.set(null);
+    }
+        
+    /** get a connection */
+    public static Connection getConnection() throws Exception {
+        if ( tranConnection.get() != null ) {
+            return tranConnection.get();
+        } else {
+            return ds.getConnection();
+        }
+    }
+    
+    public static void releaseConnection(Connection conn) throws Exception {
+        // We don't close the connection while we're in a transaction,
+        // as it needs to be used by others in the same transaction context
+        if ( tranConnection.get() == null ) {
+            conn.close();
+        }
+    }
+        
+    public static void initDatabase(String dbname, String user, String password,
+            boolean dropTables) 
+        throws Exception {
+        initDataSource(dbname, user, password);
+
+        if ( dropTables ) {
+            dropTables();
+        }
+        
+        // Assumption: if the requests table doesn't exist, none of the
+        // tables exists.  Avoids multiple queries to the database
+        if ( ! tableExists("REQUESTS") ) {
+            createTables();
+        }
+    }
+    
+    private static boolean tableExists(String tablename) throws Exception {
+        Connection conn = getConnection();
+        ResultSet rs;
+        boolean exists;
+        
+        try {
+            DatabaseMetaData md = conn.getMetaData();
+        
+            rs = md.getTables(null, "APP", tablename, null);
+            exists = rs.next();
+        } finally {
+            releaseConnection(conn);
+        }
+        
+        return exists;
+    } 
+    
+    private static void createTables() throws Exception {
+        System.out.println("Creating tables");
+        
+        executeUpdate(
+            "CREATE TABLE " + REQUESTS_TABLE + "(" +
+                "sequence_id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, " +
+                "request_type INTEGER, " +
+                "event_id VARCHAR(300), " +
+                "date VARCHAR(20), " +
+                "title VARCHAR(300), " +
+                "edit_url VARCHAR(300))");
+                        
+        executeUpdate(
+            "CREATE TABLE " + EVENTS_TABLE + "(" +
+                "event_id VARCHAR(300) PRIMARY KEY, " +
+                "date VARCHAR(20), " +
+                "title VARCHAR(300), " +
+                "edit_url VARCHAR(300), " +
+                "version_id VARCHAR(300))");             
+    }
+    
+    /**
+     * Drop the tables.  Used mostly for unit testing, to get back
+     * to a clean state
+     */
+    public static void dropTables() throws Exception {
+        try {
+            executeUpdate("DROP TABLE " + REQUESTS_TABLE);
+        } catch ( SQLException sqle ) {
+            if (! tableDoesntExist(sqle.getSQLState())) {
+                throw sqle;
+            }
+        }
+        
+        try {
+            executeUpdate("DROP TABLE " + EVENTS_TABLE);
+        } catch ( SQLException sqle ) {
+            if (! tableDoesntExist(sqle.getSQLState())) {
+                throw sqle;
+            }
+        }
+    }
+    
+    private static boolean tableDoesntExist(String sqlState) {
+        return sqlState.equals("42X05") ||
+               sqlState.equals("42Y55");
+    }
+    
+    /**
+     * Clean out the tables
+     */
+    public static void clearTables() throws Exception {
+        Connection conn = getConnection();
+        
+        try {
+            executeUpdate("DELETE FROM " + REQUESTS_TABLE);
+            executeUpdate("DELETE FROM " + EVENTS_TABLE);
+        } finally {
+            releaseConnection(conn);
+        }
+        
+    }
+    
+    /**
+     * Helper wrapper around boilerplate JDBC code.  Execute a statement
+     * that doesn't return results using a PreparedStatment, and returns 
+     * the number of rows affected
+     */
+    public static int executeUpdate(String statement) 
+            throws Exception {
+        Connection conn = getConnection();
+        try {
+           PreparedStatement ps = conn.prepareStatement(statement);
+           return ps.executeUpdate();
+        } finally {
+            releaseConnection(conn);
+        }
+    }
+    
+    /**
+     * Helper wrapper around boilerplat JDBC code.  Execute a statement
+     * that returns results using a PreparedStatement that takes no 
+     * parameters (you're on your own if you're binding parameters).
+     *
+     * @return the results from the query
+     */
+    public static ResultSet executeQueryNoParams(Connection conn, 
+            String statement) throws Exception {
+       PreparedStatement ps = conn.prepareStatement(statement);
+       return ps.executeQuery();
+    }
+}

Propchange: db/derby/code/trunk/java/demo/localcal/src/DatabaseManager.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: db/derby/code/trunk/java/demo/localcal/src/DeleteEventRequest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/demo/localcal/src/DeleteEventRequest.java?view=auto&rev=453845
==============================================================================
--- db/derby/code/trunk/java/demo/localcal/src/DeleteEventRequest.java (added)
+++ db/derby/code/trunk/java/demo/localcal/src/DeleteEventRequest.java Fri Oct  6 21:59:28 2006
@@ -0,0 +1,58 @@
+/*
+ 
+   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.
+ 
+ */
+
+/**
+ * encapsulates a request to delete an event
+ */
+public class DeleteEventRequest extends GCalendarRequest {
+    private String eventId;
+    private String editURL;
+            
+    /** Creates a new instance of AddEventRequest */
+    protected DeleteEventRequest(int requestId, String eventId, String editURL) {
+        super(requestId);
+        this.setEventId(eventId);
+        this.setEditURL(editURL);
+    }
+        
+    public String toString() {
+        return "Request # " + getId() + " to delete event id " + getEventId()
+            + " with edit URL " + editURL;
+    }
+    
+    public void submit(GCalendar calendar) throws Exception {
+        calendar.deleteEvent(editURL);
+    }
+
+    public String getEventId() {
+        return eventId;
+    }
+
+    public void setEventId(String eventId) {
+        this.eventId = eventId;
+    }    
+
+    public String getEditURL() {
+        return editURL;
+    }
+
+    public void setEditURL(String editURL) {
+        this.editURL = editURL;
+    }
+}

Propchange: db/derby/code/trunk/java/demo/localcal/src/DeleteEventRequest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: db/derby/code/trunk/java/demo/localcal/src/EventManager.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/demo/localcal/src/EventManager.java?view=auto&rev=453845
==============================================================================
--- db/derby/code/trunk/java/demo/localcal/src/EventManager.java (added)
+++ db/derby/code/trunk/java/demo/localcal/src/EventManager.java Fri Oct  6 21:59:28 2006
@@ -0,0 +1,207 @@
+/*
+ 
+   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.
+ 
+ */
+
+import java.util.*;
+import java.sql.*;
+
+/**
+ * Responsible for managing persistence of events to the database
+ */
+public class EventManager {
+    public static void addEvent(CalEvent event) throws Exception {
+        Connection conn = DatabaseManager.getConnection();
+        try {
+            PreparedStatement pstmt = conn.prepareStatement(
+                "INSERT INTO " + DatabaseManager.EVENTS_TABLE + 
+                "(event_id, date, title, edit_url, version_id) " +
+                "VALUES(?, ?, ?, ?, ?)");
+            
+            pstmt.setString(1, event.getId());
+            pstmt.setString(2, event.getDate());
+            pstmt.setString(3, event.getTitle());
+            if ( event.getEditURL() == null ) {
+                pstmt.setNull(4, Types.VARCHAR);
+            } else {
+                pstmt.setString(4, event.getEditURL());
+            }
+            
+            if ( event.getVersionId() == null ) {
+                pstmt.setNull(5, Types.VARCHAR);
+            } else {
+                pstmt.setString(5, event.getVersionId());
+            }       
+            
+            pstmt.executeUpdate();
+        } finally {
+            DatabaseManager.releaseConnection(conn);
+        }
+    }
+
+    /**
+     * Fix the event id in both the events and requests tables.  This
+     * happens when we add an event and Google Calendar sends us back
+     * a new id
+     */
+    public static void updateEventId(String oldId, String newId) 
+            throws Exception {
+        System.out.println("Updating event id in the database");
+        Connection conn = DatabaseManager.getConnection();
+        
+        try {
+            PreparedStatement pstmt = conn.prepareStatement(
+                "UPDATE " + DatabaseManager.EVENTS_TABLE + 
+                " SET event_id = ? WHERE event_id = ?");
+            pstmt.setString(1, oldId);
+            pstmt.setString(2, newId);
+            pstmt.execute();
+            
+            pstmt = conn.prepareStatement(
+                "UPDATE " + DatabaseManager.REQUESTS_TABLE + 
+                " SET event_id = ? WHERE event_id = ?");
+            pstmt.setString(1, oldId);
+            pstmt.setString(2, newId);
+            pstmt.execute();
+        } finally {
+            DatabaseManager.releaseConnection(conn);            
+        }
+    }
+
+    
+    public static void deleteEvent(String eventId) throws Exception {
+        Connection conn = DatabaseManager.getConnection();
+        try {
+            PreparedStatement pstmt = conn.prepareStatement(
+                "DELETE FROM " + DatabaseManager.EVENTS_TABLE + 
+                " WHERE event_id = ?");
+            
+            pstmt.setString(1, eventId);
+            pstmt.executeUpdate();
+        } finally {
+            DatabaseManager.releaseConnection(conn);
+        }        
+    }
+    
+    public static void updateEvent(CalEvent event) throws Exception {
+        Connection conn = DatabaseManager.getConnection();
+        try {
+            PreparedStatement pstmt = conn.prepareStatement(
+                "UPDATE " + DatabaseManager.EVENTS_TABLE + 
+                " SET date = ?, title = ?, edit_url = ?, version_id = ? " +
+                "WHERE event_id = ?");
+            
+            pstmt.setString(1, event.getDate());
+            pstmt.setString(2, event.getTitle());
+            if ( event.getEditURL() == null ) {
+                pstmt.setNull(3, Types.VARCHAR);
+            } else {
+                pstmt.setString(3, event.getEditURL());
+            }
+            if ( event.getVersionId() == null ) {
+                pstmt.setNull(4, Types.VARCHAR);
+            } else {
+                pstmt.setString(4, event.getVersionId());
+            }
+            pstmt.setString(5, event.getId());
+            if ( pstmt.executeUpdate() == 0 ) {
+                throw new Exception("Event not updated - couldn't find event " +
+                        "with id " + event.getId());
+            }
+        } finally {
+            DatabaseManager.releaseConnection(conn);
+        }
+        
+    }
+        
+    public static Collection<CalEvent> getEvents() throws Exception {
+        Connection conn = DatabaseManager.getConnection();
+        ArrayList<CalEvent> events = new ArrayList<CalEvent>();
+        
+        try {
+            ResultSet rs = DatabaseManager.executeQueryNoParams(conn, 
+                    "SELECT event_id, date, title, edit_url, version_id FROM " + 
+                    DatabaseManager.EVENTS_TABLE);
+            
+            // System.out.println("");
+            // System.out.println("Getting events from local database");
+            while ( rs.next() ) {
+                CalEvent event = new CalEvent(
+                    rs.getString(1), rs.getString(2), rs.getString(3),
+                    rs.getString(4), rs.getString(5));
+               
+                // System.out.println(event);
+                events.add(event);
+            }
+            // System.out.println("");
+        } finally {
+            DatabaseManager.releaseConnection(conn);
+        }
+        
+        return events;
+    }
+    
+    /**
+     * Get a single event from the database
+     */
+    public static CalEvent getEvent(String eventId) throws Exception {
+        Connection conn = DatabaseManager.getConnection();
+        CalEvent event;
+        
+        try {
+            PreparedStatement pstmt = conn.prepareStatement( 
+                    "SELECT date, title, edit_url, version_id FROM " + 
+                    DatabaseManager.EVENTS_TABLE +
+                    " WHERE event_id = ?");
+            pstmt.setString(1, eventId);
+            ResultSet rs = pstmt.executeQuery();
+            
+            if ( ! rs.next() )  {
+                return null;
+            }
+            
+            event = new CalEvent(
+                eventId, rs.getString(1), rs.getString(2),
+                rs.getString(3), rs.getString(4));
+        } finally {
+            DatabaseManager.releaseConnection(conn);
+        }
+        
+        return event;
+    }
+
+    /**
+     * Refresh the database with a list of events from the Google
+     * Calendar.  Google Calendar is The Truth and the local store
+     * must submit...
+     *
+     * Note we also clear out any pending requests, as they are no
+     * longer valid once we've refreshed.  
+     */
+    public static void refresh(Collection<CalEvent> events) 
+        throws Exception {
+        System.out.println("Refreshing local store with list of events " +
+                "from Google Calendar...");
+        
+        DatabaseManager.clearTables();
+        
+        for ( CalEvent event : events ) {
+            addEvent(event);
+        }
+    }
+}
+

Propchange: db/derby/code/trunk/java/demo/localcal/src/EventManager.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: db/derby/code/trunk/java/demo/localcal/src/GCalendar.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/demo/localcal/src/GCalendar.java?view=auto&rev=453845
==============================================================================
--- db/derby/code/trunk/java/demo/localcal/src/GCalendar.java (added)
+++ db/derby/code/trunk/java/demo/localcal/src/GCalendar.java Fri Oct  6 21:59:28 2006
@@ -0,0 +1,441 @@
+/*
+ 
+   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.
+ 
+ */
+
+import java.util.*;
+import java.util.concurrent.*;
+import java.net.URL;
+import java.security.*;
+import java.text.SimpleDateFormat;
+
+import com.google.gdata.client.calendar.*;
+import com.google.gdata.data.extensions.*;
+import com.google.gdata.data.*;
+import com.google.gdata.data.calendar.*;
+import com.google.gdata.util.*;
+import com.google.gdata.client.*;
+
+/**
+ * This class provides a simple abstraction for interacting with the
+ * Google Calendar Data API.  It runs as a separate thread so that requests
+ * can be posted asynchronously.
+ *
+ * All requests are shipped through the requestQueue.  The run method
+ * reads from the request queue and ships the requests to Google Calendar.
+ * Results are then fed back on the response queue.
+ */
+public class GCalendar {
+    private static String GOOGLE_CAL_URL_PREFIX = 
+            "http://www.google.com/calendar/feeds/";
+    
+    private static String AUTHOR_NAME="David Van Couvering";
+    private static String AUTHOR_EMAIL="david.vancouvering@gmail.com";
+            
+    private String          calendarId;    
+    private URL             feedUrl;
+    private EventFeed       feed;
+    private CalendarService service;
+    private String          gmtOffset;
+    private String          startDay;
+    private String          endDay;
+    
+    /** 
+     * Creates a new instance of GCalendarService 
+     *
+     * @param calendarId
+     *      The special token that identifies the calendar.  
+     *      @see DerbyCalendarApplet.connect() for a full description of this.
+     * 
+     * @param user
+     *      Your Google Calendar account name, e.g. david.vancouvering@gmail.com
+     *
+     * @param password
+     *      Your Google Calendar password
+     *
+     * * @param startDay
+     *      the starting day for the calendar, in the format <yyyy>-<mm>-<dd>
+     *
+     * @param endDay
+     *      the starting day for the calendar, in the format <yyyy>-<mm>-<dd>
+
+     */
+    public GCalendar(String calendarId, String gmtOffset, final String user, 
+            final String password, String startDay, String endDay) 
+            throws Exception {
+        this.calendarId     = calendarId;
+        this.gmtOffset      = gmtOffset;
+        this.startDay       = startDay;
+        this.endDay         = endDay;
+        
+        // We're accessing a read-write feed, and we want full information
+        this.feedUrl = new URL(GOOGLE_CAL_URL_PREFIX + calendarId + 
+                "/private/full");   
+
+        try {
+            AccessController.doPrivileged(
+                new PrivilegedExceptionAction<Object>() {
+                    public Object run() throws Exception {
+                        service = new CalendarService("DerbyCalendar Demo");
+                        service.setUserCredentials(user, password);
+
+                        // Send the request and receive the response:
+                        feed = service.getFeed(feedUrl, EventFeed.class);
+
+                        return null;
+                    }
+                }
+            );
+        } catch (PrivilegedActionException e) {
+            handleException(e.getException(), 
+                    "Unable to get Google Calendar feed",
+                    null);
+        }
+        
+    }
+    
+    /**
+     * Add an event to the Google Calendar
+     *
+     * @param event
+     *      The event to add
+     *
+     * @return 
+     *      The event that was added, modified as needed by what was
+     *      returned from Google Calendar.  In particular, the id has
+     *      been updated to be the id provided by Google Calendar.
+     */
+    public CalEvent addEvent(String date, String title) 
+        throws Exception {                
+        EventEntry newEntry = new EventEntry();
+
+        newEntry.setTitle(new PlainTextConstruct(title));
+
+        Person author = new Person(AUTHOR_NAME, null, AUTHOR_EMAIL);
+        newEntry.getAuthors().add(author);
+
+        DateTime startTime = DateTime.parseDateTime(date + "T00:00:00" + 
+                gmtOffset);
+        startTime.setDateOnly(true);
+        
+        // An all day event has an end time of the next day.  Need to
+        // calculate that using the wonderful Java date APIs...
+        String endTimeString = getNextDay(date);
+        DateTime endTime = DateTime.parseDateTime(endTimeString + "" +
+                "T00:00:00" + gmtOffset);
+        endTime.setDateOnly(true);
+
+        When eventTimes = new When();
+        eventTimes.setStartTime(startTime);
+        eventTimes.setEndTime(endTime);
+        newEntry.addTime(eventTimes);
+        
+
+        // Need a 'final' version of the entry so it can be used
+        // inside the privileged block. 
+        final EventEntry finalEntry = newEntry;
+        
+        // Send the request.  The response contains the final version of the
+        // entry.  In particular, we need to store the edit URL for future
+        // updates
+        EventEntry addedEntry = null;        
+        try {
+            addedEntry = AccessController.doPrivileged(
+                new PrivilegedExceptionAction<EventEntry>() {
+                    public EventEntry run() throws Exception {
+                        return service.insert(feedUrl, finalEntry);
+                    }
+                }
+            );
+        } catch (PrivilegedActionException e) {
+            handleException(e.getException(), "Unable to add event",
+                    finalEntry);
+        }
+        
+        return new CalEvent(addedEntry.getId(), date, title,
+                addedEntry.getEditLink().getHref(),
+                addedEntry.getVersionId());
+    }
+    
+    /**
+     * Get the next day given a day string of the format <yyyy>-<mm>-<dd>
+     */
+    private String getNextDay(String day) throws Exception {
+        // See how simple it is to add a day to a date? :)
+        // Thanks be to Google for finding this code...
+        java.text.SimpleDateFormat sdf = 
+            new java.text.SimpleDateFormat("yyyy-MM-dd");
+        Date date = sdf.parse(day);
+        
+        Calendar cal = Calendar.getInstance(); 
+        cal.setTime(date); 
+        cal.add(Calendar.DATE, 1);
+        return sdf.format(cal.getTime());
+    }
+    
+    /**
+     * Update an event
+     *
+     * @param id
+     *      The id of the event to be updated
+     *
+     * @param title
+     *      The new title for the event - that's the only thing we
+     *      allow to be changed at this time -- keeping things simple.
+     *
+     * @return
+     *  A new instance of an event, with fields updated as necessary
+     *  from Google (e.g. version id has changed)
+     */
+    public CalEvent updateEvent(CalEvent event) throws 
+            Exception {
+        final EventEntry entry = createEventEntry(event);
+        final URL editURL = new URL(event.getEditURL());
+        
+        // Send the request. 
+        EventEntry updatedEntry = null;        
+        try {
+            updatedEntry = AccessController.doPrivileged(
+                new PrivilegedExceptionAction<EventEntry>() {
+                    public EventEntry run() throws Exception {
+                        return service.update(editURL, entry);
+                    }
+                }
+            );
+        } catch (PrivilegedActionException e) {
+            handleException(e.getException(), "Unable to update event",
+                    entry);
+        }     
+        
+        return createDerbyCalEvent(updatedEntry);
+    }
+    
+    /**
+     * Delete an event
+     *
+     * @param editURLString
+     *      The string representation of the edit URL
+     */
+    public void deleteEvent(final String editURLString) throws Exception {
+        final URL editURL = new URL(editURLString);
+        
+        // Send the request. ry;        
+        try {
+            AccessController.doPrivileged(
+                new PrivilegedExceptionAction<Object>() {
+                    public Object run() throws Exception {
+                        service.delete(editURL);
+                        return null;
+                    }
+                }
+            );
+        } catch (PrivilegedActionException e) {
+            handleException(e.getException(),
+                "Unable to delete event with edit URL " + editURLString, null);
+        }        
+    }
+
+    /**
+     * Get the list of events.  You would want to
+     * use this, for example, if you are trying to get the calendar when
+     * the application first starts up.
+     *
+     * @return a HashMap of events for the calendar, where the hash id
+     *      is the id of the event
+     */
+    public Collection<CalEvent> getEvents() throws Exception {
+        
+        CalendarEventFeed resultFeed = null;
+
+        // Do this in a privileged block, as it connects to the Internet
+        try {
+            resultFeed = AccessController.doPrivileged(
+                new PrivilegedExceptionAction<CalendarEventFeed>() {
+                    public CalendarEventFeed run() throws Exception {
+                        CalendarQuery myQuery = new CalendarQuery(feedUrl);
+
+                        myQuery.setMinimumStartTime(DateTime.parseDateTime(
+                                startDay + "T00:00:00" + gmtOffset));
+                        myQuery.setMaximumStartTime(DateTime.parseDateTime(
+                                endDay + "T23:59:59" + gmtOffset));
+
+                        // Send the request and receive the response:
+                        return service.query(myQuery, CalendarEventFeed.class);
+                    }
+                }
+            );
+        } catch (PrivilegedActionException e) {
+            handleException(e.getException(), "Unable to get entries from " +
+                    "Google Calendar", null);
+        }
+        
+        // Build a list of DerbyCal events based on these entries
+        ArrayList<CalEvent> calentries = new ArrayList<CalEvent>();
+
+        // debugging
+        // System.out.println("Entries from Google Calendar:");
+                
+        for ( EventEntry entry : resultFeed.getEntries() ) {
+            calentries.add(createDerbyCalEvent(entry));            
+        }
+
+        return calentries;
+    }
+    
+    public void clearCalendar() throws Exception {
+        // Get the latest list of entries
+        Collection<CalEvent> events = getEvents();
+        
+        // Delete the entries one at a time
+        for ( CalEvent event : events ) {
+            deleteEvent(event.getEditURL());
+        }
+    }
+    
+    /**
+     * Create a Google EventEntry from an event object
+     */
+    private EventEntry createEventEntry(CalEvent event) throws Exception {
+        EventEntry entry = new EventEntry();
+        entry.setTitle(new PlainTextConstruct(event.getTitle()));
+        entry.setId(event.getId());
+
+        Person author = new Person(AUTHOR_NAME, null, AUTHOR_EMAIL);
+        entry.getAuthors().add(author);
+
+        DateTime startTime = DateTime.parseDateTime(event.getDate() + "T00:00:00" + 
+                gmtOffset);
+        startTime.setDateOnly(true);
+        
+        // An all day event has an end time of the next day.  Need to
+        // calculate that using the wonderful Java date APIs...
+        String endTimeString = getNextDay(event.getDate());
+        DateTime endTime = DateTime.parseDateTime(endTimeString + "" +
+                "T00:00:00" + gmtOffset);
+        endTime.setDateOnly(true);
+
+        When eventTimes = new When();
+        eventTimes.setStartTime(startTime);
+        eventTimes.setEndTime(endTime);
+        entry.addTime(eventTimes);
+        
+        return entry;
+    }
+    
+    /**
+     * Create a DerbyCalEvent from an EventEntry
+     */
+    private CalEvent createDerbyCalEvent(EventEntry entry) throws Exception {
+        String title = entry.getTitle().getPlainText();
+
+        // Simplifying assumption: all entries are day-long events.
+        // No more, no less
+        List<When> times = entry.getTimes();
+        String start = times.get(0).getStartTime().toString();
+
+        String editURL = entry.getEditLink().getHref();
+
+        CalEvent event = new CalEvent(entry.getId(), start, title, 
+            editURL, entry.getVersionId());
+        
+        return event;
+    }
+    
+    /**
+     * Handle an exception received when invoking a method on Google Calendar
+     *
+     * @param e
+     *      The exception that was encountered.  Obtain this by calling
+     *      getException() on the PrivilegedActionException
+     *
+     * @param message
+     *      This is the message specific to the invocation that got the exception,
+     *      such as "unable to update event".  This is prepended upon the details
+     *      provided by this method
+     *
+     * @param entry
+     *      The event entry associated with this message, if available.  Otherwise
+     *      set this to null.
+     *
+     * @throws Exception
+     *      When the exception encountered should result in an exception being thrown
+     *      to the caller.  This is almost always the case.
+     */
+    private void handleException(Exception e, String message, EventEntry entry) 
+        throws Exception {
+
+        String entryInfo = "";
+        String id;
+        String title;
+        String date;
+        if ( entry != null ) {
+            id = parseId(entry.getId());
+            title = entry.getTitle().getPlainText();
+            List<When> times = entry.getTimes();
+            date = times.get(0).getStartTime().toString();
+                        
+            entryInfo = date + ": " + title;
+        }
+        if ( e instanceof java.net.NoRouteToHostException ) {
+            throw new NetworkDownException(e);
+        }
+        if ( e instanceof ResourceNotFoundException ) {
+            message += ": the event " + entryInfo + " does not appear " +
+                    "to exist.  Perhaps somebody else deleted it?";
+        }
+        else if ( e instanceof ServiceException ) {
+            ServiceException se = (ServiceException)e;
+
+            // Let's see if this is a conflict where we are trying to
+            // update an entry that has been modified.  The error code
+            // is 400, which is not helpful, it's supposed to be 403,
+            // "conflict", but oh well...
+            String responseBody = se.getResponseBody();
+            
+            if ( responseBody != null && 
+                    responseBody.contains("Sequence ids from event") &&
+                    responseBody.contains("do not match")) {
+                message += ": unable to update event " +
+                        entryInfo + ", it appears someone else has updated it." +
+                        " The event will be updated to contain the latest " +
+                        " version from Google Calendar.";
+            }
+            else {
+                message += ": exception from Google Calendar.  HTTP error code " +
+                        "is " + se.getHttpErrorCodeOverride() + "; response body is: " +
+                        se.getResponseBody();
+            }
+        } else {
+            message += ": please see next exception for details.";
+        }
+        
+        throw new Exception(message, e);
+
+     }
+    
+    /** 
+     * Strip off the unique id of the entry from the full Google
+     * Calendar id, which includes the Feed URL
+     */
+    private String parseId(String id) {
+        if ( id == null ) {
+            return null;
+        }
+        return id.substring(id.lastIndexOf("/") + 1, id.length());
+    }
+                
+}

Propchange: db/derby/code/trunk/java/demo/localcal/src/GCalendar.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: db/derby/code/trunk/java/demo/localcal/src/GCalendarRequest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/demo/localcal/src/GCalendarRequest.java?view=auto&rev=453845
==============================================================================
--- db/derby/code/trunk/java/demo/localcal/src/GCalendarRequest.java (added)
+++ db/derby/code/trunk/java/demo/localcal/src/GCalendarRequest.java Fri Oct  6 21:59:28 2006
@@ -0,0 +1,47 @@
+/*
+ 
+   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.
+ 
+ */
+
+/**
+ * Encapsulates a request that we're posting to Google Calendar.
+ * Subtypes provide type-safe mechanisms for providing parameters
+ * to the request and for the result data, if any.
+ */
+public abstract class GCalendarRequest implements java.io.Serializable {  
+    private int requestId;
+    
+    public GCalendarRequest(int id) {
+        setId(id);
+    }
+    
+    public void setId(int id) {
+        this.requestId = id;
+    }
+    
+    public int getId() {
+        return requestId;
+    }
+    
+    /**
+     * Submit the request to the Google Calendar service.  What this
+     * means depends on the type of request.
+     */
+    public abstract void submit(GCalendar calendar) throws Exception ;
+    
+    public abstract String toString();
+}

Propchange: db/derby/code/trunk/java/demo/localcal/src/GCalendarRequest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: db/derby/code/trunk/java/demo/localcal/src/NetworkDownException.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/demo/localcal/src/NetworkDownException.java?view=auto&rev=453845
==============================================================================
--- db/derby/code/trunk/java/demo/localcal/src/NetworkDownException.java (added)
+++ db/derby/code/trunk/java/demo/localcal/src/NetworkDownException.java Fri Oct  6 21:59:28 2006
@@ -0,0 +1,32 @@
+/*
+ 
+   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.
+ 
+ */
+
+/**
+ * Indicate that the network is down
+ */
+public class NetworkDownException extends Exception {
+    public NetworkDownException(Throwable cause) {
+        super("Unable to communicate with Google Calendar; going offline", 
+                cause);
+    }   
+    
+    public NetworkDownException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}

Propchange: db/derby/code/trunk/java/demo/localcal/src/NetworkDownException.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: db/derby/code/trunk/java/demo/localcal/src/RequestManager.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/demo/localcal/src/RequestManager.java?view=auto&rev=453845
==============================================================================
--- db/derby/code/trunk/java/demo/localcal/src/RequestManager.java (added)
+++ db/derby/code/trunk/java/demo/localcal/src/RequestManager.java Fri Oct  6 21:59:28 2006
@@ -0,0 +1,316 @@
+/*
+ 
+   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.
+ 
+ */
+
+import java.util.*;
+import java.security.*;
+import java.sql.*;
+import javax.sql.*;
+
+/**
+ * This class is resonsible for managing requests while offline
+ */
+public class RequestManager {
+    
+    private static ArrayList<String> conflicts;
+    
+    // Request types
+    public static final int ADD_EVENT = 1;
+    public static final int DELETE_EVENT = 2;
+    public static final int UPDATE_EVENT = 3;
+    
+    private static final String INSERT_REQUEST_SQL =
+        "INSERT INTO " + DatabaseManager.REQUESTS_TABLE +
+            "(request_type, event_id, date, title, edit_url) " +
+            "VALUES(?, ?, ?, ?, ?)";
+        
+    public static void storeAddEvent(String eventId, String date, 
+        String title) throws Exception  {
+        Connection conn = DatabaseManager.getConnection();
+        
+        try {
+            // Insert the request to add an event
+            PreparedStatement pstmt = conn.prepareStatement(
+                INSERT_REQUEST_SQL);
+
+            pstmt.setInt(1, ADD_EVENT);
+            pstmt.setString(2, eventId);
+            pstmt.setString(3, date);
+            pstmt.setString(4, title);
+            pstmt.setNull(5, Types.VARCHAR);
+            pstmt.executeUpdate();
+        } finally {
+            DatabaseManager.releaseConnection(conn);
+        }
+    }
+    
+    public static void storeUpdateEvent(CalEvent event) 
+            throws Exception {
+        Connection conn = DatabaseManager.getConnection();
+        
+        try {
+            // First see if this event was added while we were offline.
+            // If so, just update that request.  Otherwise add it as a
+            // separate request.  Trying to save round trips here, and also
+            // avoids the issue around the fact that Google changes the id
+            int sequenceId = findAddRequest(event.getId());
+            if  ( sequenceId > 0 ) {
+                System.out.println("Request to update an event that was added " +
+                        "while offline; updating the add event request instead");
+                updateAddRequest(sequenceId, event.getTitle());
+            } else {
+                // insert the request to update an event
+                PreparedStatement pstmt = conn.prepareStatement(
+                    INSERT_REQUEST_SQL);
+                pstmt.setInt(1, UPDATE_EVENT);
+                pstmt.setString(2, event.getId());
+                pstmt.setNull(3, Types.VARCHAR);
+                pstmt.setString(4, event.getTitle());
+                pstmt.setString(5, event.getEditURL());
+
+                pstmt.executeUpdate();
+            }            
+        } finally {
+            DatabaseManager.releaseConnection(conn);
+        }        
+    }
+    
+    /** 
+     * This updates an existing request to add an event, rather than adding
+     * a new update request
+     */
+    private static void updateAddRequest(int sequenceId, String title) 
+            throws Exception {
+        Connection conn = DatabaseManager.getConnection();
+        
+        try {
+            PreparedStatement pstmt = conn.prepareStatement(
+                "UPDATE " + DatabaseManager.REQUESTS_TABLE +
+                    " SET title = ? WHERE sequence_id = ?");
+            pstmt.setString(1, title);
+            pstmt.setInt(2, sequenceId);
+            pstmt.executeUpdate();
+        } finally {
+            DatabaseManager.releaseConnection(conn);
+        }        
+    }
+    
+    public static void storeDeleteEvent(String eventId, String editURL) 
+            throws Exception {
+        Connection conn = DatabaseManager.getConnection();
+        
+        try {
+            // First, see if this event was added while we were offline.
+            // If so, just delete the add event request and we're done.
+            // Otherwise log the delete event request
+            int sequenceId = findAddRequest(eventId);
+            if ( sequenceId > 0 ) {
+                System.out.println("Request to delete an event that was " +
+                        "added while offline -  deleting add event " +
+                        "request instead...");
+                deleteRequestsForEvent(eventId);
+            } else {
+                // insert the request to delete an event
+                PreparedStatement pstmt = conn.prepareStatement(
+                    INSERT_REQUEST_SQL);
+                pstmt.setInt(1, DELETE_EVENT);
+                pstmt.setString(2, eventId);
+                pstmt.setNull(3, Types.VARCHAR);
+                pstmt.setNull(4, Types.VARCHAR);
+                pstmt.setString(5, editURL);                
+
+                pstmt.executeUpdate();
+            }
+        } finally {
+            DatabaseManager.releaseConnection(conn);
+        }        
+        
+    }
+    
+    private static int findAddRequest(String eventId) throws Exception {
+        Connection conn = DatabaseManager.getConnection();
+        
+        try {
+            PreparedStatement pstmt = conn.prepareStatement(
+                "SELECT sequence_id FROM " + DatabaseManager.REQUESTS_TABLE +
+                    " WHERE request_type = ? AND event_id = ?");
+            pstmt.setInt(1, ADD_EVENT);
+            pstmt.setString(2, eventId);
+            ResultSet rs = pstmt.executeQuery();
+            if ( rs.next() ) {
+                return rs.getInt(1);
+            } else {
+                return 0;
+            }
+        } finally {
+            DatabaseManager.releaseConnection(conn);
+        }
+    }
+    
+    private static void deleteRequestsForEvent(String eventId) throws Exception {
+        Connection conn = DatabaseManager.getConnection();
+        
+        try {
+            PreparedStatement pstmt = conn.prepareStatement(
+                "DELETE FROM " + DatabaseManager.REQUESTS_TABLE +
+                    " WHERE event_id = ?");
+            pstmt.setString(1, eventId);
+            pstmt.executeUpdate();
+        } finally {
+            DatabaseManager.releaseConnection(conn);
+        }
+    }
+
+    /** 
+     * Submit all stored requests to Google Calendar.  This process
+     * is logged to System.out.  If the network goes down during this
+     * time, we stop where we were and throw a NetworkDownException.
+     *
+     * If there is an error communicating with the local database we throw
+     * a SQLException.
+     *
+     * Any other exceptions are logged and we continue on until all
+     * requests are processed.  Each request is deleted from the database
+     * once it is processed, successfully or not.
+     *
+     * @return the number of failures during this process
+     */
+    public static int submitRequests(GCalendar calendar) throws Exception {
+        List<GCalendarRequest> requests = getRequests();
+        
+        //
+        // Go through the requests in order
+        int failures = 0;
+        int totalRequests = requests.size();
+        
+        System.out.println("");
+        System.out.println("==== SUBMITTING PENDING REQUESTS TO GOOGLE CALENDAR ====");
+        
+        conflicts = new ArrayList<String>();
+        
+        for ( GCalendarRequest request : requests ) {
+            try {
+                request.submit(calendar);
+                System.out.println(request + " submitted successfully");
+                deleteRequest(request);
+            } catch ( NetworkDownException nde ) {
+                throw nde;
+            } catch ( SQLException sqle ) {
+                // this is pretty severe, we need to bail
+                throw sqle;
+            } catch ( Exception e ) {
+                System.out.println("ERROR submitting " + request + ": " +
+                   e.getMessage());
+                conflicts.add(e.getMessage());
+                // e.printStackTrace();
+                failures++;
+                deleteRequest(request);
+            }
+        }
+        
+        System.out.println("==== DONE - " + totalRequests + " requests submitted ==== ");
+        System.out.println("");
+        
+        // Now clean out the request table
+                
+        return failures;
+    }
+    
+    public static List<String> getConflicts() {
+        return conflicts;
+    }
+    
+    /**
+     * Delete a request from the database
+     */
+    private static void deleteRequest(GCalendarRequest request) throws Exception {
+        Connection conn = DatabaseManager.getConnection();
+        try {
+            PreparedStatement pstmt = conn.prepareStatement(
+                "DELETE FROM "  + DatabaseManager.REQUESTS_TABLE + 
+                " WHERE sequence_id = ?");
+            
+            pstmt.setInt(1, request.getId());
+            pstmt.executeUpdate();
+        } finally {
+            DatabaseManager.releaseConnection(conn);
+        }
+    }
+    
+    private static List<GCalendarRequest> getRequests() throws Exception {
+        Connection conn = DatabaseManager.getConnection();
+        
+        try {
+            ArrayList<GCalendarRequest> requests = 
+                    new ArrayList<GCalendarRequest>();
+            ResultSet rs = DatabaseManager.executeQueryNoParams(conn,
+                "SELECT sequence_id, request_type, event_id, date, title, " +
+                    "edit_url FROM " + DatabaseManager.REQUESTS_TABLE +
+                    " ORDER BY sequence_id");
+
+            while ( rs.next() ) {
+                GCalendarRequest request = createRequestFromRow(rs);
+                requests.add(request);
+            }
+
+            return requests;
+        } finally {
+            DatabaseManager.releaseConnection(conn);
+        }
+    }
+    
+    /**
+     * Get a request from a result set.  Assumes the folowing columns in
+     * the result set:
+     * <ul>
+     * <li>sequence_id
+     * <li>request type
+     * <li>event id
+     * <li>date
+     * <li>title
+     * </ul>
+     */
+    private static GCalendarRequest createRequestFromRow(ResultSet rs) throws Exception {
+        GCalendarRequest request;
+        
+        // These might be null, depending on the type, but we can get them anyway
+        int sequenceId = rs.getInt(1);
+        int type = rs.getInt(2); 
+        String eventId = rs.getString(3);
+        String date = rs.getString(4);
+        String title = rs.getString(5);
+        String editURL = rs.getString(6);
+        
+        switch ( type ) {
+            case ADD_EVENT:
+                request = new AddEventRequest(sequenceId, eventId, date, title);
+                break;
+            case UPDATE_EVENT:
+                request = new UpdateEventRequest(sequenceId, eventId, title);
+                break;
+            case DELETE_EVENT:
+                request = new DeleteEventRequest(sequenceId, eventId, editURL);
+                break;
+            default:
+                throw new Exception("Unrecognized event type " + type +
+                    "for request with sequence id " + sequenceId);
+        }
+        
+        return request;
+    }    
+}

Propchange: db/derby/code/trunk/java/demo/localcal/src/RequestManager.java
------------------------------------------------------------------------------
    svn:eol-style = native