Index: /trunk/greenstone3-extensions/gsdl-as/create-tables.sql
===================================================================
--- /trunk/greenstone3-extensions/gsdl-as/create-tables.sql	(revision 8887)
+++ /trunk/greenstone3-extensions/gsdl-as/create-tables.sql	(revision 8888)
@@ -30,2 +30,15 @@
 	unique (subscription,predicate)
 );
+
+create table events (
+	id int primary key auto_increment,
+	timestamp datetime not null,
+	content text not null
+);
+
+create table events_to_subs (
+	event int not null references events.id,
+	subscription int not null references subscriptions.id,
+
+	unique (event,subscription)
+);
Index: /trunk/greenstone3-extensions/gsdl-as/lib/templates/events.vm
===================================================================
--- /trunk/greenstone3-extensions/gsdl-as/lib/templates/events.vm	(revision 8888)
+++ /trunk/greenstone3-extensions/gsdl-as/lib/templates/events.vm	(revision 8888)
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>Greenstone Digital Library Software</title>
+<link href="default.css" rel="stylesheet" type="text/css"/>
+</head>
+<body>
+<div id="content">
+<h3>Alerting Service for Greenstone</h3>
+<p>Events for subscription $subscription.name</p>
+<p class="description">$subscription</p>
+<p><a href="/alerting/service?action=showFeed&amp;subscriptionID=$subscription.id">show as feed</a></p>
+#foreach ($event in $list)
+<p>$event</p>
+#end
+<a href="/alerting">back</a> to the alerting service
+</div>
+
+</body>
+</html>
Index: /trunk/greenstone3-extensions/gsdl-as/lib/templates/feed.vm
===================================================================
--- /trunk/greenstone3-extensions/gsdl-as/lib/templates/feed.vm	(revision 8887)
+++ /trunk/greenstone3-extensions/gsdl-as/lib/templates/feed.vm	(revision 8888)
@@ -1,13 +1,13 @@
 <?xml version="1.0"?>
-<?xml-stylesheet type="text/xsl" href="/alerting/rss2html.xsl"?>
+<!--<?xml-stylesheet type="text/xml" href="/alerting/rss2html.xml"?>-->
 <rss version="2.0">
    <channel>
-      <title>$title</title>
-      <link>TODO</link>
-      <description>TDOD</description>
+      <title>Events for Subscription $subscription.name</title>
+      <link>/alerting/service?action=showFeed&amp;subscriptionID=$subscription.id</link>
+      <description>TODO</description>
       <language>en</language>
-      <lastBuildDate>TODO</lastBuildDate>
+      <lastBuildDate>$lastBuildDate</lastBuildDate>
       <generator>Alerting Service for Greenstone</generator>
-      <webMaster>TODO</webMaster>
+      <webMaster>$webmaster</webMaster>
       
       #foreach($event in $list)
Index: /trunk/greenstone3-extensions/gsdl-as/lib/templates/list.vm
===================================================================
--- /trunk/greenstone3-extensions/gsdl-as/lib/templates/list.vm	(revision 8887)
+++ /trunk/greenstone3-extensions/gsdl-as/lib/templates/list.vm	(revision 8888)
@@ -14,6 +14,6 @@
 	<div class="subscription">
 		<p><span class="sub_id">Subscription $sub.Id</span><br/>
-		<a href="service?action=showFeed&subscriptionID=$sub.Id">show matching events</a> | <a href="service?action=editSubscription&subscriptionID=$sub.Id">edit subscription</a> | <a href="service?action=deleteSubscription&subscriptionID=$sub.Id">delete subscription</a></p>
-		<table class="subscription">
+		<a href="service?action=showEvents&subscriptionID=$sub.Id">show matching events</a> | <a href="service?action=editSubscription&subscriptionID=$sub.Id">edit subscription</a> | <a href="service?action=deleteSubscription&subscriptionID=$sub.Id">delete subscription</a></p>
+<!--		<table class="subscription">
 			<thead>
 				<tr><th>Field</th><th>Predicate</th></tr>
@@ -28,5 +28,8 @@
 				#end
 			</tbody>
-		</table>
+		</table> // -->
+		<pre>
+		$sub
+		</pre>
 		<br/>
 	</div>
Index: /trunk/greenstone3-extensions/gsdl-as/lib/templates/sub_type-details.vm
===================================================================
--- /trunk/greenstone3-extensions/gsdl-as/lib/templates/sub_type-details.vm	(revision 8887)
+++ /trunk/greenstone3-extensions/gsdl-as/lib/templates/sub_type-details.vm	(revision 8888)
@@ -4,5 +4,5 @@
 <html xmlns="http://www.w3.org/1999/xhtml">
 	<head>
-		<title>Andrea Schweer</title>
+		<title>Create a subscription</title>
 		<link rel="stylesheet" href="/alerting/form.css" type="text/css"/>
 	</head>
Index: /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/AlertingService.java
===================================================================
--- /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/AlertingService.java	(revision 8887)
+++ /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/AlertingService.java	(revision 8888)
@@ -10,5 +10,4 @@
 
 import java.lang.reflect.Method;
-import java.net.MalformedURLException;
 import java.net.URL;
 import java.sql.SQLException;
@@ -24,4 +23,5 @@
 import org.greenstone.gsdlas.database.DatabaseException;
 import org.greenstone.gsdlas.profiles.Predicate;
+import org.greenstone.gsdlas.profiles.Subscription;
 import org.greenstone.gsdlas.users.UserManagementException;
 import org.greenstone.gsdlas.users.UserManager;
@@ -58,4 +58,5 @@
             "deleteSubscription",
             "editSubscription",
+            "showEvents",
             "showFeed",
             "listSubscriptions",
@@ -175,7 +176,32 @@
     }
     
-    public String showFeed(Map arguments, Context context) {
-        context.put("list", ""/* TODO pass subscription list */);
+    public String showFeed(Map arguments, Context context) throws NumberFormatException, DatabaseException, SQLException {
+        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
+        if (!UserManager.getInstance().isLoggedIn(session)) {
+            session.setAttribute("next_action", "showFeed");
+            return showLoginForm(arguments, context);
+        }
+        String subscriptionID = (String) arguments.get("subscriptionID");
+        Integer subID = new Integer(subscriptionID);
+        Set events = EventStore.getInstance().getEvents(subID);
+        context.put("list", events);
+        context.put("subscription", ProfileStore.getInstance().getSubscription(subID.intValue()));
+        HttpServletResponse res = (HttpServletResponse) context.get(RESPONSE);
+        res.setContentType("text/xml");
         return "feed.vm";
+    }
+    
+    public String showEvents(Map arguments, Context context) throws NumberFormatException, DatabaseException, SQLException {
+        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
+        if (!UserManager.getInstance().isLoggedIn(session)) {
+            session.setAttribute("next_action", "showEvents");
+            return showLoginForm(arguments, context);
+        }
+        String subscriptionID = (String) arguments.get("subscriptionID");
+        Integer subID = new Integer(subscriptionID);
+        Set events = EventStore.getInstance().getEvents(subID);
+        context.put("list", events);
+        context.put("subscription", ProfileStore.getInstance().getSubscription(subID.intValue()));
+        return "events.vm";
     }
     
@@ -250,4 +276,5 @@
         for (Iterator iter = rawEvent.keySet().iterator(); iter.hasNext();) {
             String key = (String) iter.next();
+            if (key.equals("action")) continue; // we don't want this
             String[] value = (String[]) rawEvent.get(key);
             event.put(key, value[0]);
@@ -259,14 +286,19 @@
             String hostID = (String) event.get(Constants.HOST_ID_FIELD);
             gsComm = new GreenstoneCommunicator(new URL(hostID));
-        } catch (MalformedURLException e) {
-            // TODO Auto-generated catch block
+        } catch (Exception e) {
+            System.err.println("Can't communicate to Greenstone: " + e.getMessage());
             e.printStackTrace();
+        }
+        
+        Set matchedSubscriptions = ProfileStore.getInstance().filter(event, gsComm);
+        
+        try {
+            EventStore.getInstance().add(event, matchedSubscriptions);
+            Notifier.getInstance().sendNotifications(event, matchedSubscriptions);
         } catch (Exception e) {
-            // TODO Auto-generated catch block
+            System.err.println("Couldn't save events: " + e.getMessage());
             e.printStackTrace();
         }
-        Set matchedSubscriptions = ProfileStore.getInstance().filter(event, gsComm);
         System.out.println(matchedSubscriptions.size() + " matching subscriptions: " + matchedSubscriptions);
-        // TODO do something with the matched subscriptions
     }
 
@@ -322,8 +354,13 @@
      */
     private String showSubscriptionWizardPage(Map arguments, Context context, boolean create) throws Exception {
+        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
         if (create) {
             context.put(Constants.ACTION_PARAM, "createSubscription");
         } else {
+            // prefill from existing subscription
             context.put(Constants.ACTION_PARAM, "editSubscription");
+            String subscriptionID = (String) arguments.get("subscriptionID");
+            int subID = Integer.parseInt(subscriptionID);
+            prefillFromSubscription(session, subID);
         }
         if (!arguments.containsKey("current_page")) {
@@ -331,5 +368,4 @@
         }
         
-        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
         String currentPage = (String) arguments.get("current_page");
         String direction = (String) arguments.get("next_page");
@@ -412,4 +448,21 @@
 
     /**
+     * @param session
+     * @param subID
+     */
+    private void prefillFromSubscription(HttpSession session, int subID) {
+        Subscription sub = ProfileStore.getInstance().getSubscription(subID);
+        Map typeArgs = new HashMap();
+        // TODO really fill stuff
+        savePageArgsToSession("type-details", typeArgs, session);
+        Map hostArgs = new HashMap();
+        savePageArgsToSession("host", hostArgs, session);
+        Map collArgs = new HashMap();
+        savePageArgsToSession("collection", collArgs, session);
+        Map notificationArgs = new HashMap();
+        savePageArgsToSession("notification", notificationArgs, session);
+    }
+
+    /**
      * @param nextPage
      * @param session
Index: /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/EventStore.java
===================================================================
--- /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/EventStore.java	(revision 8888)
+++ /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/EventStore.java	(revision 8888)
@@ -0,0 +1,169 @@
+/*
+ * Created on Nov 23, 2004
+ * Copyright (C) Andrea Schweer, 2004
+ *
+ * This file is part of the Greenstone Alerting Service.
+ * Refer to the COPYING file in the base directory of this package
+ * for licensing information.
+ */
+package org.greenstone.gsdlas;
+
+import java.sql.*;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.Date;
+
+import org.greenstone.gsdlas.database.DatabaseException;
+import org.greenstone.gsdlas.database.DatabaseManager;
+import org.greenstone.gsdlas.profiles.Subscription;
+
+/**
+ * @author schweer
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class EventStore {
+
+    private static EventStore instance;
+    
+    private EventStore() {
+    }
+    
+    /**
+     * @return
+     */
+    public static EventStore getInstance() {
+        if (instance == null) instance = new EventStore();
+        return instance;
+    }
+    
+    public void add(Map event, Set subscriptions) throws DatabaseException, SQLException, ParseException {
+        Connection conn = DatabaseManager.getInstance().getDatabaseConnection();
+        Statement statement = conn.createStatement();
+
+        int eventID = saveEventToDatabase(event);
+        
+        for (Iterator iter = subscriptions.iterator(); iter.hasNext();) {
+            Subscription sub = (Subscription) iter.next();
+            int subID = sub.getId();
+            String sqlString = "INSERT INTO events_to_subs (event,subscription) " +
+            		"VALUES (" + eventID + "," + subID + ");";
+            statement.executeUpdate(sqlString);
+        }
+    }
+
+    /**
+     * @param event
+     * @return
+     * @throws DatabaseException
+     * @throws SQLException
+     * @throws ParseException
+     */
+    private int saveEventToDatabase(Map event) throws DatabaseException, SQLException, ParseException {
+        String eventString = mapAsString(event);
+        String timestamp = (String) event.get("timestamp");
+        timestamp = convertToSQLDatetime(timestamp);
+        
+        Connection conn = DatabaseManager.getInstance().getDatabaseConnection();
+        Statement statement = conn.createStatement();
+        String query = "SELECT id FROM events WHERE timestamp = " + timestamp + 
+        	" AND content = '" + eventString + "';";
+        ResultSet results = statement.executeQuery(query);
+        if (results.next()) {
+            return results.getInt("id");
+        }
+        
+        String insert = "INSERT INTO events (timestamp,content) " +
+        		"VALUES (" + timestamp + ",'" + eventString + "');"; 
+        statement.executeUpdate(insert);
+        results = statement.executeQuery(query);
+        if (results.next()) {
+            return results.getInt("id");
+        }
+        throw new DatabaseException("couldn't save event to database");
+    }
+
+    /**
+     * converts a timestamp from an event to a format recognised by MySQL
+     * @param timestamp
+     * @return
+     * @throws ParseException
+     */
+    private String convertToSQLDatetime(String timestamp) throws ParseException {
+        Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z").parse(timestamp);
+        return new SimpleDateFormat("yyyyMMddHHmmss").format(date);
+    }
+
+    /**
+     * @param event
+     */
+    private String encodeSpecialChars(String string) {
+        String encodedString = string.replace("&", "&amp;");
+        encodedString = encodedString.replace("<", "&lt;");
+        encodedString = encodedString.replace(">", "&gt;");
+        encodedString = encodedString.replace("'", "&apos;");
+        encodedString = encodedString.replace("\"", "&quot;");
+        return encodedString;
+    }
+
+    /**
+     * @param subscriptionID
+     * @return
+     * @throws DatabaseException
+     * @throws SQLException
+     */
+    public Set getEvents(Integer subscriptionID) throws DatabaseException, SQLException {
+        Set result = new HashSet(); // can't use TreeSet, because we put Maps in there, and Map isn't instanceof Comparable
+        Connection conn = DatabaseManager.getInstance().getDatabaseConnection();
+        Statement statement = conn.createStatement();
+        String query = "SELECT content " +
+        		"FROM events e JOIN events_to_subs ets ON (e.id = ets.event) " +
+        		"WHERE ets.subscription = " + subscriptionID.intValue() + ";";
+        ResultSet results = statement.executeQuery(query);
+        while (results.next()) {
+            Map event = stringAsMap(results.getString("content"));
+            result.add(event);
+        }
+        return result;
+    }
+
+    private String mapAsString(Map map) {
+        StringBuffer buffer = new StringBuffer();
+        for (Iterator iter = map.keySet().iterator(); iter.hasNext();) {
+            String key = (String) iter.next();
+            String value = (String) map.get(key);
+            String encodedValue = encodeSpecialChars(value);
+            buffer.append(key);
+            buffer.append(">"); // > has been encoded to &gt; in the original String
+            buffer.append(encodedValue);
+            if (iter.hasNext()) {
+                buffer.append(">>");
+            }
+        }
+        return buffer.toString();
+    }
+    
+    private Map stringAsMap(String string) {
+        Map map = new HashMap();
+        String[] lines = string.split(">>");
+        for (int i = 0; i < lines.length; i++) {
+            String line = lines[i];
+            String[] columns = line.split(">");
+            String key = columns[0];
+            String value = null;
+            if (columns.length > 1) {
+                value = columns[1];
+            }
+            map.put(key, value);
+        }
+        return map;
+    }
+    
+    public void cleanUp() {
+        // TODO implement method 
+        // delete all events that are too old (?)
+        // delete all events that don't match any subscriptions
+    }
+}
Index: /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/Notifier.java
===================================================================
--- /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/Notifier.java	(revision 8888)
+++ /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/Notifier.java	(revision 8888)
@@ -0,0 +1,96 @@
+/*
+ * Created on Jan 1, 2005
+ * Copyright (C) 2004, 2005 Andrea Schweer
+ *
+ * This file is part of the Greenstone Alerting Service.
+ * Refer to the COPYING file in the base directory of this package
+ * for licensing information.
+ */
+package org.greenstone.gsdlas;
+
+import java.util.*;
+import java.util.Map;
+import java.util.Set;
+
+import javax.mail.MessagingException;
+
+import org.greenstone.gsdlas.profiles.Subscription;
+import org.greenstone.gsdlas.util.Mailer;
+
+/**
+ * @author andrea
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ * 
+ * Singleton
+ */
+public class Notifier {
+
+    private static Notifier instance; 
+    
+    private Notifier () {
+        // hide default constructor
+    }
+    
+    /**
+     * @return
+     */
+    public static Notifier getInstance() {
+        if (instance == null) instance = new Notifier();
+        return instance;
+    }
+
+    /**
+     * @param event
+     * @param matchedSubscriptions
+     */
+    public void sendNotifications(Map event, Set matchedSubscriptions) {
+        // TODO read smtp host and sender from configuration file
+        Mailer mailer = new Mailer(System.getProperty("user.name") + "@localhost", "localhost");
+        String eventText = eventToEmail(event);
+        String subject = "Event for your subscription";
+        
+        for (Iterator iter = matchedSubscriptions.iterator(); iter.hasNext();) {
+            Subscription sub = (Subscription) iter.next();
+            if (!sub.wantsEMailNotification()) continue;
+
+            StringBuffer message = new StringBuffer();
+            // TODO formatting of notification email via velocity?
+            message.append("You've created a subscription at the Greenstone Alerting Service running on " +
+            		"localhost:\n\n");
+            message.append(sub.toString());
+            message.append("\n");
+            message.append("An event has occurred that matches this subscription:\n\n");
+            message.append(eventText);
+            message.append("\n");
+            message.append("Go to http://localhost:8080/alerting/service?action=listSubscriptions to " +
+            		"change your subscriptions, or to http://localhost:8080/alerting/service?action=showEvents&subscriptionID=" + 
+            		sub.getId() + " to see all events that match the subscription.\n");
+            
+            String to = sub.getMailAddress();
+            
+            try {
+                mailer.sendMail(to, subject, message.toString());
+            } catch (MessagingException e) {
+                System.err.println("Couldn't notify for subscription " + sub.getId() + ": " + e.getMessage());
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * @param event
+     * @return
+     */
+    private String eventToEmail(Map event) {
+        StringBuffer message = new StringBuffer();
+        for (Iterator iter = event.keySet().iterator(); iter.hasNext();) {
+            String key = (String) iter.next();
+            String value = (String) event.get(key);
+            message.append(key + ": " + value + "\n");
+        }
+        return message.toString();
+    }
+
+}
Index: /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/ProfileStore.java
===================================================================
--- /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/ProfileStore.java	(revision 8887)
+++ /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/ProfileStore.java	(revision 8888)
@@ -405,4 +405,12 @@
         }
     }
+
+    /**
+     * @param i
+     * @return
+     */
+    public Subscription getSubscription(int id) {
+        return (Subscription) subscriptions.get(new Integer(id));
+    }
     
 }
Index: /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/database/DatabaseManager.java
===================================================================
--- /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/database/DatabaseManager.java	(revision 8887)
+++ /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/database/DatabaseManager.java	(revision 8888)
@@ -9,4 +9,5 @@
 package org.greenstone.gsdlas.database;
 
+import java.sql.*;
 import java.sql.Connection;
 import java.sql.DriverManager;
@@ -42,4 +43,8 @@
                 database = DriverManager.getConnection(connectionString);
             } catch (Exception e) {
+                if (e instanceof SQLException) {
+                    SQLException sqle = (SQLException) e;
+                    System.err.println("SQLState: " + sqle.getSQLState());
+                }
                 throw new DatabaseException("Could not establish connection to database (" + connectionString + ")", e);
             }
Index: /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/PredicateFactory.java
===================================================================
--- /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/PredicateFactory.java	(revision 8887)
+++ /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/PredicateFactory.java	(revision 8888)
@@ -1,5 +1,5 @@
 /*
  * Created on Nov 1, 2004
- * Copyright (C) Andrea Schweer, 2004
+ * Copyright (C) 2004, 2005 Andrea Schweer
  *
  * This file is part of the Greenstone Alerting Service.
Index: /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/QueryPredicate.java
===================================================================
--- /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/QueryPredicate.java	(revision 8887)
+++ /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/QueryPredicate.java	(revision 8888)
@@ -31,3 +31,7 @@
         return true;
     }
+    
+    public String toString() {
+        return field + " matches " + value;
+    }
 }
Index: /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/Subscription.java
===================================================================
--- /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/Subscription.java	(revision 8887)
+++ /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/Subscription.java	(revision 8888)
@@ -188,5 +188,29 @@
     
     public String toString() {
-        return "Subscription id " + id + ": " + map;
+        StringBuffer result = new StringBuffer("Subscription ");
+        result.append(name != null ? name : "(no name) ");
+        result.append(" (id ");
+        result.append(id);
+        result.append("):\n");
+        for (Iterator iter = map.keySet().iterator(); iter.hasNext();) {
+            String key = (String) iter.next();
+            Object value = map.get(key);
+            if (value instanceof Predicate) {
+                Predicate pred = (Predicate) value;
+                result.append(pred);
+            }
+            if (value instanceof List) {
+                List list = (List) value;
+                for (Iterator iterator = list.iterator(); iterator.hasNext();) {
+                    Predicate pred = (Predicate) iterator.next();
+                    result.append(pred);
+                    if (iterator.hasNext()) {
+                        result.append(" or ");
+                    }
+                }
+            }
+            result.append("\n");
+        }
+        return result.toString();
     }
     
@@ -255,4 +279,22 @@
         return id;
     }
+
+    /**
+     * @return
+     */
+    public boolean wantsEMailNotification() {
+        return email != null && email.length() > 1;
+    }
+
+    /**
+     * @return
+     */
+    public String getMailAddress() {
+        return email;
+    }
+    
+    public String getName() {
+        return name;
+    }
     
 }
Index: /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/SubstringMatchPredicate.java
===================================================================
--- /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/SubstringMatchPredicate.java	(revision 8887)
+++ /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/SubstringMatchPredicate.java	(revision 8888)
@@ -33,3 +33,7 @@
     }
 
+    public String toString() {
+        return field + " contains " + value;
+    }
+    
 }
Index: /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/util/Mailer.java
===================================================================
--- /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/util/Mailer.java	(revision 8888)
+++ /trunk/greenstone3-extensions/gsdl-as/src/org/greenstone/gsdlas/util/Mailer.java	(revision 8888)
@@ -0,0 +1,58 @@
+/*
+ * Created on Jan 1, 2005
+ * Copyright (C) 2004, 2005 Andrea Schweer
+ *
+ * This file is part of the Greenstone Alerting Service.
+ * Refer to the COPYING file in the base directory of this package
+ * for licensing information.
+ */
+package org.greenstone.gsdlas.util;
+
+import java.util.Properties;
+
+import javax.mail.*;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+
+/**
+ * @author schweer
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class Mailer {
+    
+    private String fromAddress;
+    private String mailHost;
+    
+    public Mailer (String from, String host) {
+        fromAddress = from;
+        mailHost = host;
+    }
+    
+    public void sendMail(String to, String subject, String message) throws MessagingException {
+        // Set the host smtp address
+        Properties props = new Properties();
+        props.put("mail.smtp.host", mailHost);
+        
+        // create some properties and get the default Session
+        //Session session = Session.getDefaultInstance(props, null);
+        Session session = Session.getInstance(props);
+        
+        // create a message
+        Message msg = new MimeMessage(session);
+        
+        // set the from and to address
+        InternetAddress addressFrom = new InternetAddress(fromAddress);
+        msg.setFrom(addressFrom);
+        
+        InternetAddress[] addressTo = new InternetAddress[1];
+        addressTo[0] = new InternetAddress(to);
+        msg.setRecipients(Message.RecipientType.TO, addressTo);
+        
+        // Setting the Subject and Content Type
+        msg.setSubject(subject);
+        msg.setContent(message, "text/plain");
+        Transport.send(msg);
+    }
+}
Index: /trunk/greenstone3-extensions/gsdl-as/test-src/org/greenstone/gsdlas/util/MailerTest.java
===================================================================
--- /trunk/greenstone3-extensions/gsdl-as/test-src/org/greenstone/gsdlas/util/MailerTest.java	(revision 8888)
+++ /trunk/greenstone3-extensions/gsdl-as/test-src/org/greenstone/gsdlas/util/MailerTest.java	(revision 8888)
@@ -0,0 +1,35 @@
+/*
+ * Created on Jan 1, 2005
+ *
+ * TODO To change the template for this generated file go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+package org.greenstone.gsdlas.util;
+
+import javax.mail.MessagingException;
+
+import junit.framework.TestCase;
+
+/**
+ * @author andrea
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class MailerTest extends TestCase {
+
+    public static void main(String[] args) {
+        junit.textui.TestRunner.run(MailerTest.class);
+    }
+
+    public final void testSendMail() {
+        Mailer mailer = new Mailer("andrea@localhost", "localhost");
+        try {
+            mailer.sendMail("andrea@localhost", "test", "test message testing...\n");
+        } catch (MessagingException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+}
Index: /trunk/gsdl3/extensions/gsdl-as/create-tables.sql
===================================================================
--- /trunk/gsdl3/extensions/gsdl-as/create-tables.sql	(revision 8887)
+++ /trunk/gsdl3/extensions/gsdl-as/create-tables.sql	(revision 8888)
@@ -30,2 +30,15 @@
 	unique (subscription,predicate)
 );
+
+create table events (
+	id int primary key auto_increment,
+	timestamp datetime not null,
+	content text not null
+);
+
+create table events_to_subs (
+	event int not null references events.id,
+	subscription int not null references subscriptions.id,
+
+	unique (event,subscription)
+);
Index: /trunk/gsdl3/extensions/gsdl-as/lib/templates/events.vm
===================================================================
--- /trunk/gsdl3/extensions/gsdl-as/lib/templates/events.vm	(revision 8888)
+++ /trunk/gsdl3/extensions/gsdl-as/lib/templates/events.vm	(revision 8888)
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>Greenstone Digital Library Software</title>
+<link href="default.css" rel="stylesheet" type="text/css"/>
+</head>
+<body>
+<div id="content">
+<h3>Alerting Service for Greenstone</h3>
+<p>Events for subscription $subscription.name</p>
+<p class="description">$subscription</p>
+<p><a href="/alerting/service?action=showFeed&amp;subscriptionID=$subscription.id">show as feed</a></p>
+#foreach ($event in $list)
+<p>$event</p>
+#end
+<a href="/alerting">back</a> to the alerting service
+</div>
+
+</body>
+</html>
Index: /trunk/gsdl3/extensions/gsdl-as/lib/templates/feed.vm
===================================================================
--- /trunk/gsdl3/extensions/gsdl-as/lib/templates/feed.vm	(revision 8887)
+++ /trunk/gsdl3/extensions/gsdl-as/lib/templates/feed.vm	(revision 8888)
@@ -1,13 +1,13 @@
 <?xml version="1.0"?>
-<?xml-stylesheet type="text/xsl" href="/alerting/rss2html.xsl"?>
+<!--<?xml-stylesheet type="text/xml" href="/alerting/rss2html.xml"?>-->
 <rss version="2.0">
    <channel>
-      <title>$title</title>
-      <link>TODO</link>
-      <description>TDOD</description>
+      <title>Events for Subscription $subscription.name</title>
+      <link>/alerting/service?action=showFeed&amp;subscriptionID=$subscription.id</link>
+      <description>TODO</description>
       <language>en</language>
-      <lastBuildDate>TODO</lastBuildDate>
+      <lastBuildDate>$lastBuildDate</lastBuildDate>
       <generator>Alerting Service for Greenstone</generator>
-      <webMaster>TODO</webMaster>
+      <webMaster>$webmaster</webMaster>
       
       #foreach($event in $list)
Index: /trunk/gsdl3/extensions/gsdl-as/lib/templates/list.vm
===================================================================
--- /trunk/gsdl3/extensions/gsdl-as/lib/templates/list.vm	(revision 8887)
+++ /trunk/gsdl3/extensions/gsdl-as/lib/templates/list.vm	(revision 8888)
@@ -14,6 +14,6 @@
 	<div class="subscription">
 		<p><span class="sub_id">Subscription $sub.Id</span><br/>
-		<a href="service?action=showFeed&subscriptionID=$sub.Id">show matching events</a> | <a href="service?action=editSubscription&subscriptionID=$sub.Id">edit subscription</a> | <a href="service?action=deleteSubscription&subscriptionID=$sub.Id">delete subscription</a></p>
-		<table class="subscription">
+		<a href="service?action=showEvents&subscriptionID=$sub.Id">show matching events</a> | <a href="service?action=editSubscription&subscriptionID=$sub.Id">edit subscription</a> | <a href="service?action=deleteSubscription&subscriptionID=$sub.Id">delete subscription</a></p>
+<!--		<table class="subscription">
 			<thead>
 				<tr><th>Field</th><th>Predicate</th></tr>
@@ -28,5 +28,8 @@
 				#end
 			</tbody>
-		</table>
+		</table> // -->
+		<pre>
+		$sub
+		</pre>
 		<br/>
 	</div>
Index: /trunk/gsdl3/extensions/gsdl-as/lib/templates/sub_type-details.vm
===================================================================
--- /trunk/gsdl3/extensions/gsdl-as/lib/templates/sub_type-details.vm	(revision 8887)
+++ /trunk/gsdl3/extensions/gsdl-as/lib/templates/sub_type-details.vm	(revision 8888)
@@ -4,5 +4,5 @@
 <html xmlns="http://www.w3.org/1999/xhtml">
 	<head>
-		<title>Andrea Schweer</title>
+		<title>Create a subscription</title>
 		<link rel="stylesheet" href="/alerting/form.css" type="text/css"/>
 	</head>
Index: /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/AlertingService.java
===================================================================
--- /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/AlertingService.java	(revision 8887)
+++ /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/AlertingService.java	(revision 8888)
@@ -10,5 +10,4 @@
 
 import java.lang.reflect.Method;
-import java.net.MalformedURLException;
 import java.net.URL;
 import java.sql.SQLException;
@@ -24,4 +23,5 @@
 import org.greenstone.gsdlas.database.DatabaseException;
 import org.greenstone.gsdlas.profiles.Predicate;
+import org.greenstone.gsdlas.profiles.Subscription;
 import org.greenstone.gsdlas.users.UserManagementException;
 import org.greenstone.gsdlas.users.UserManager;
@@ -58,4 +58,5 @@
             "deleteSubscription",
             "editSubscription",
+            "showEvents",
             "showFeed",
             "listSubscriptions",
@@ -175,7 +176,32 @@
     }
     
-    public String showFeed(Map arguments, Context context) {
-        context.put("list", ""/* TODO pass subscription list */);
+    public String showFeed(Map arguments, Context context) throws NumberFormatException, DatabaseException, SQLException {
+        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
+        if (!UserManager.getInstance().isLoggedIn(session)) {
+            session.setAttribute("next_action", "showFeed");
+            return showLoginForm(arguments, context);
+        }
+        String subscriptionID = (String) arguments.get("subscriptionID");
+        Integer subID = new Integer(subscriptionID);
+        Set events = EventStore.getInstance().getEvents(subID);
+        context.put("list", events);
+        context.put("subscription", ProfileStore.getInstance().getSubscription(subID.intValue()));
+        HttpServletResponse res = (HttpServletResponse) context.get(RESPONSE);
+        res.setContentType("text/xml");
         return "feed.vm";
+    }
+    
+    public String showEvents(Map arguments, Context context) throws NumberFormatException, DatabaseException, SQLException {
+        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
+        if (!UserManager.getInstance().isLoggedIn(session)) {
+            session.setAttribute("next_action", "showEvents");
+            return showLoginForm(arguments, context);
+        }
+        String subscriptionID = (String) arguments.get("subscriptionID");
+        Integer subID = new Integer(subscriptionID);
+        Set events = EventStore.getInstance().getEvents(subID);
+        context.put("list", events);
+        context.put("subscription", ProfileStore.getInstance().getSubscription(subID.intValue()));
+        return "events.vm";
     }
     
@@ -250,4 +276,5 @@
         for (Iterator iter = rawEvent.keySet().iterator(); iter.hasNext();) {
             String key = (String) iter.next();
+            if (key.equals("action")) continue; // we don't want this
             String[] value = (String[]) rawEvent.get(key);
             event.put(key, value[0]);
@@ -259,14 +286,19 @@
             String hostID = (String) event.get(Constants.HOST_ID_FIELD);
             gsComm = new GreenstoneCommunicator(new URL(hostID));
-        } catch (MalformedURLException e) {
-            // TODO Auto-generated catch block
+        } catch (Exception e) {
+            System.err.println("Can't communicate to Greenstone: " + e.getMessage());
             e.printStackTrace();
+        }
+        
+        Set matchedSubscriptions = ProfileStore.getInstance().filter(event, gsComm);
+        
+        try {
+            EventStore.getInstance().add(event, matchedSubscriptions);
+            Notifier.getInstance().sendNotifications(event, matchedSubscriptions);
         } catch (Exception e) {
-            // TODO Auto-generated catch block
+            System.err.println("Couldn't save events: " + e.getMessage());
             e.printStackTrace();
         }
-        Set matchedSubscriptions = ProfileStore.getInstance().filter(event, gsComm);
         System.out.println(matchedSubscriptions.size() + " matching subscriptions: " + matchedSubscriptions);
-        // TODO do something with the matched subscriptions
     }
 
@@ -322,8 +354,13 @@
      */
     private String showSubscriptionWizardPage(Map arguments, Context context, boolean create) throws Exception {
+        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
         if (create) {
             context.put(Constants.ACTION_PARAM, "createSubscription");
         } else {
+            // prefill from existing subscription
             context.put(Constants.ACTION_PARAM, "editSubscription");
+            String subscriptionID = (String) arguments.get("subscriptionID");
+            int subID = Integer.parseInt(subscriptionID);
+            prefillFromSubscription(session, subID);
         }
         if (!arguments.containsKey("current_page")) {
@@ -331,5 +368,4 @@
         }
         
-        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
         String currentPage = (String) arguments.get("current_page");
         String direction = (String) arguments.get("next_page");
@@ -412,4 +448,21 @@
 
     /**
+     * @param session
+     * @param subID
+     */
+    private void prefillFromSubscription(HttpSession session, int subID) {
+        Subscription sub = ProfileStore.getInstance().getSubscription(subID);
+        Map typeArgs = new HashMap();
+        // TODO really fill stuff
+        savePageArgsToSession("type-details", typeArgs, session);
+        Map hostArgs = new HashMap();
+        savePageArgsToSession("host", hostArgs, session);
+        Map collArgs = new HashMap();
+        savePageArgsToSession("collection", collArgs, session);
+        Map notificationArgs = new HashMap();
+        savePageArgsToSession("notification", notificationArgs, session);
+    }
+
+    /**
      * @param nextPage
      * @param session
Index: /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/EventStore.java
===================================================================
--- /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/EventStore.java	(revision 8888)
+++ /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/EventStore.java	(revision 8888)
@@ -0,0 +1,169 @@
+/*
+ * Created on Nov 23, 2004
+ * Copyright (C) Andrea Schweer, 2004
+ *
+ * This file is part of the Greenstone Alerting Service.
+ * Refer to the COPYING file in the base directory of this package
+ * for licensing information.
+ */
+package org.greenstone.gsdlas;
+
+import java.sql.*;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.Date;
+
+import org.greenstone.gsdlas.database.DatabaseException;
+import org.greenstone.gsdlas.database.DatabaseManager;
+import org.greenstone.gsdlas.profiles.Subscription;
+
+/**
+ * @author schweer
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class EventStore {
+
+    private static EventStore instance;
+    
+    private EventStore() {
+    }
+    
+    /**
+     * @return
+     */
+    public static EventStore getInstance() {
+        if (instance == null) instance = new EventStore();
+        return instance;
+    }
+    
+    public void add(Map event, Set subscriptions) throws DatabaseException, SQLException, ParseException {
+        Connection conn = DatabaseManager.getInstance().getDatabaseConnection();
+        Statement statement = conn.createStatement();
+
+        int eventID = saveEventToDatabase(event);
+        
+        for (Iterator iter = subscriptions.iterator(); iter.hasNext();) {
+            Subscription sub = (Subscription) iter.next();
+            int subID = sub.getId();
+            String sqlString = "INSERT INTO events_to_subs (event,subscription) " +
+            		"VALUES (" + eventID + "," + subID + ");";
+            statement.executeUpdate(sqlString);
+        }
+    }
+
+    /**
+     * @param event
+     * @return
+     * @throws DatabaseException
+     * @throws SQLException
+     * @throws ParseException
+     */
+    private int saveEventToDatabase(Map event) throws DatabaseException, SQLException, ParseException {
+        String eventString = mapAsString(event);
+        String timestamp = (String) event.get("timestamp");
+        timestamp = convertToSQLDatetime(timestamp);
+        
+        Connection conn = DatabaseManager.getInstance().getDatabaseConnection();
+        Statement statement = conn.createStatement();
+        String query = "SELECT id FROM events WHERE timestamp = " + timestamp + 
+        	" AND content = '" + eventString + "';";
+        ResultSet results = statement.executeQuery(query);
+        if (results.next()) {
+            return results.getInt("id");
+        }
+        
+        String insert = "INSERT INTO events (timestamp,content) " +
+        		"VALUES (" + timestamp + ",'" + eventString + "');"; 
+        statement.executeUpdate(insert);
+        results = statement.executeQuery(query);
+        if (results.next()) {
+            return results.getInt("id");
+        }
+        throw new DatabaseException("couldn't save event to database");
+    }
+
+    /**
+     * converts a timestamp from an event to a format recognised by MySQL
+     * @param timestamp
+     * @return
+     * @throws ParseException
+     */
+    private String convertToSQLDatetime(String timestamp) throws ParseException {
+        Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z").parse(timestamp);
+        return new SimpleDateFormat("yyyyMMddHHmmss").format(date);
+    }
+
+    /**
+     * @param event
+     */
+    private String encodeSpecialChars(String string) {
+        String encodedString = string.replace("&", "&amp;");
+        encodedString = encodedString.replace("<", "&lt;");
+        encodedString = encodedString.replace(">", "&gt;");
+        encodedString = encodedString.replace("'", "&apos;");
+        encodedString = encodedString.replace("\"", "&quot;");
+        return encodedString;
+    }
+
+    /**
+     * @param subscriptionID
+     * @return
+     * @throws DatabaseException
+     * @throws SQLException
+     */
+    public Set getEvents(Integer subscriptionID) throws DatabaseException, SQLException {
+        Set result = new HashSet(); // can't use TreeSet, because we put Maps in there, and Map isn't instanceof Comparable
+        Connection conn = DatabaseManager.getInstance().getDatabaseConnection();
+        Statement statement = conn.createStatement();
+        String query = "SELECT content " +
+        		"FROM events e JOIN events_to_subs ets ON (e.id = ets.event) " +
+        		"WHERE ets.subscription = " + subscriptionID.intValue() + ";";
+        ResultSet results = statement.executeQuery(query);
+        while (results.next()) {
+            Map event = stringAsMap(results.getString("content"));
+            result.add(event);
+        }
+        return result;
+    }
+
+    private String mapAsString(Map map) {
+        StringBuffer buffer = new StringBuffer();
+        for (Iterator iter = map.keySet().iterator(); iter.hasNext();) {
+            String key = (String) iter.next();
+            String value = (String) map.get(key);
+            String encodedValue = encodeSpecialChars(value);
+            buffer.append(key);
+            buffer.append(">"); // > has been encoded to &gt; in the original String
+            buffer.append(encodedValue);
+            if (iter.hasNext()) {
+                buffer.append(">>");
+            }
+        }
+        return buffer.toString();
+    }
+    
+    private Map stringAsMap(String string) {
+        Map map = new HashMap();
+        String[] lines = string.split(">>");
+        for (int i = 0; i < lines.length; i++) {
+            String line = lines[i];
+            String[] columns = line.split(">");
+            String key = columns[0];
+            String value = null;
+            if (columns.length > 1) {
+                value = columns[1];
+            }
+            map.put(key, value);
+        }
+        return map;
+    }
+    
+    public void cleanUp() {
+        // TODO implement method 
+        // delete all events that are too old (?)
+        // delete all events that don't match any subscriptions
+    }
+}
Index: /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/Notifier.java
===================================================================
--- /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/Notifier.java	(revision 8888)
+++ /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/Notifier.java	(revision 8888)
@@ -0,0 +1,96 @@
+/*
+ * Created on Jan 1, 2005
+ * Copyright (C) 2004, 2005 Andrea Schweer
+ *
+ * This file is part of the Greenstone Alerting Service.
+ * Refer to the COPYING file in the base directory of this package
+ * for licensing information.
+ */
+package org.greenstone.gsdlas;
+
+import java.util.*;
+import java.util.Map;
+import java.util.Set;
+
+import javax.mail.MessagingException;
+
+import org.greenstone.gsdlas.profiles.Subscription;
+import org.greenstone.gsdlas.util.Mailer;
+
+/**
+ * @author andrea
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ * 
+ * Singleton
+ */
+public class Notifier {
+
+    private static Notifier instance; 
+    
+    private Notifier () {
+        // hide default constructor
+    }
+    
+    /**
+     * @return
+     */
+    public static Notifier getInstance() {
+        if (instance == null) instance = new Notifier();
+        return instance;
+    }
+
+    /**
+     * @param event
+     * @param matchedSubscriptions
+     */
+    public void sendNotifications(Map event, Set matchedSubscriptions) {
+        // TODO read smtp host and sender from configuration file
+        Mailer mailer = new Mailer(System.getProperty("user.name") + "@localhost", "localhost");
+        String eventText = eventToEmail(event);
+        String subject = "Event for your subscription";
+        
+        for (Iterator iter = matchedSubscriptions.iterator(); iter.hasNext();) {
+            Subscription sub = (Subscription) iter.next();
+            if (!sub.wantsEMailNotification()) continue;
+
+            StringBuffer message = new StringBuffer();
+            // TODO formatting of notification email via velocity?
+            message.append("You've created a subscription at the Greenstone Alerting Service running on " +
+            		"localhost:\n\n");
+            message.append(sub.toString());
+            message.append("\n");
+            message.append("An event has occurred that matches this subscription:\n\n");
+            message.append(eventText);
+            message.append("\n");
+            message.append("Go to http://localhost:8080/alerting/service?action=listSubscriptions to " +
+            		"change your subscriptions, or to http://localhost:8080/alerting/service?action=showEvents&subscriptionID=" + 
+            		sub.getId() + " to see all events that match the subscription.\n");
+            
+            String to = sub.getMailAddress();
+            
+            try {
+                mailer.sendMail(to, subject, message.toString());
+            } catch (MessagingException e) {
+                System.err.println("Couldn't notify for subscription " + sub.getId() + ": " + e.getMessage());
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * @param event
+     * @return
+     */
+    private String eventToEmail(Map event) {
+        StringBuffer message = new StringBuffer();
+        for (Iterator iter = event.keySet().iterator(); iter.hasNext();) {
+            String key = (String) iter.next();
+            String value = (String) event.get(key);
+            message.append(key + ": " + value + "\n");
+        }
+        return message.toString();
+    }
+
+}
Index: /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/ProfileStore.java
===================================================================
--- /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/ProfileStore.java	(revision 8887)
+++ /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/ProfileStore.java	(revision 8888)
@@ -405,4 +405,12 @@
         }
     }
+
+    /**
+     * @param i
+     * @return
+     */
+    public Subscription getSubscription(int id) {
+        return (Subscription) subscriptions.get(new Integer(id));
+    }
     
 }
Index: /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/database/DatabaseManager.java
===================================================================
--- /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/database/DatabaseManager.java	(revision 8887)
+++ /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/database/DatabaseManager.java	(revision 8888)
@@ -9,4 +9,5 @@
 package org.greenstone.gsdlas.database;
 
+import java.sql.*;
 import java.sql.Connection;
 import java.sql.DriverManager;
@@ -42,4 +43,8 @@
                 database = DriverManager.getConnection(connectionString);
             } catch (Exception e) {
+                if (e instanceof SQLException) {
+                    SQLException sqle = (SQLException) e;
+                    System.err.println("SQLState: " + sqle.getSQLState());
+                }
                 throw new DatabaseException("Could not establish connection to database (" + connectionString + ")", e);
             }
Index: /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/PredicateFactory.java
===================================================================
--- /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/PredicateFactory.java	(revision 8887)
+++ /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/PredicateFactory.java	(revision 8888)
@@ -1,5 +1,5 @@
 /*
  * Created on Nov 1, 2004
- * Copyright (C) Andrea Schweer, 2004
+ * Copyright (C) 2004, 2005 Andrea Schweer
  *
  * This file is part of the Greenstone Alerting Service.
Index: /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/QueryPredicate.java
===================================================================
--- /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/QueryPredicate.java	(revision 8887)
+++ /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/QueryPredicate.java	(revision 8888)
@@ -31,3 +31,7 @@
         return true;
     }
+    
+    public String toString() {
+        return field + " matches " + value;
+    }
 }
Index: /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/Subscription.java
===================================================================
--- /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/Subscription.java	(revision 8887)
+++ /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/Subscription.java	(revision 8888)
@@ -188,5 +188,29 @@
     
     public String toString() {
-        return "Subscription id " + id + ": " + map;
+        StringBuffer result = new StringBuffer("Subscription ");
+        result.append(name != null ? name : "(no name) ");
+        result.append(" (id ");
+        result.append(id);
+        result.append("):\n");
+        for (Iterator iter = map.keySet().iterator(); iter.hasNext();) {
+            String key = (String) iter.next();
+            Object value = map.get(key);
+            if (value instanceof Predicate) {
+                Predicate pred = (Predicate) value;
+                result.append(pred);
+            }
+            if (value instanceof List) {
+                List list = (List) value;
+                for (Iterator iterator = list.iterator(); iterator.hasNext();) {
+                    Predicate pred = (Predicate) iterator.next();
+                    result.append(pred);
+                    if (iterator.hasNext()) {
+                        result.append(" or ");
+                    }
+                }
+            }
+            result.append("\n");
+        }
+        return result.toString();
     }
     
@@ -255,4 +279,22 @@
         return id;
     }
+
+    /**
+     * @return
+     */
+    public boolean wantsEMailNotification() {
+        return email != null && email.length() > 1;
+    }
+
+    /**
+     * @return
+     */
+    public String getMailAddress() {
+        return email;
+    }
+    
+    public String getName() {
+        return name;
+    }
     
 }
Index: /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/SubstringMatchPredicate.java
===================================================================
--- /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/SubstringMatchPredicate.java	(revision 8887)
+++ /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/profiles/SubstringMatchPredicate.java	(revision 8888)
@@ -33,3 +33,7 @@
     }
 
+    public String toString() {
+        return field + " contains " + value;
+    }
+    
 }
Index: /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/util/Mailer.java
===================================================================
--- /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/util/Mailer.java	(revision 8888)
+++ /trunk/gsdl3/extensions/gsdl-as/src/org/greenstone/gsdlas/util/Mailer.java	(revision 8888)
@@ -0,0 +1,58 @@
+/*
+ * Created on Jan 1, 2005
+ * Copyright (C) 2004, 2005 Andrea Schweer
+ *
+ * This file is part of the Greenstone Alerting Service.
+ * Refer to the COPYING file in the base directory of this package
+ * for licensing information.
+ */
+package org.greenstone.gsdlas.util;
+
+import java.util.Properties;
+
+import javax.mail.*;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+
+/**
+ * @author schweer
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class Mailer {
+    
+    private String fromAddress;
+    private String mailHost;
+    
+    public Mailer (String from, String host) {
+        fromAddress = from;
+        mailHost = host;
+    }
+    
+    public void sendMail(String to, String subject, String message) throws MessagingException {
+        // Set the host smtp address
+        Properties props = new Properties();
+        props.put("mail.smtp.host", mailHost);
+        
+        // create some properties and get the default Session
+        //Session session = Session.getDefaultInstance(props, null);
+        Session session = Session.getInstance(props);
+        
+        // create a message
+        Message msg = new MimeMessage(session);
+        
+        // set the from and to address
+        InternetAddress addressFrom = new InternetAddress(fromAddress);
+        msg.setFrom(addressFrom);
+        
+        InternetAddress[] addressTo = new InternetAddress[1];
+        addressTo[0] = new InternetAddress(to);
+        msg.setRecipients(Message.RecipientType.TO, addressTo);
+        
+        // Setting the Subject and Content Type
+        msg.setSubject(subject);
+        msg.setContent(message, "text/plain");
+        Transport.send(msg);
+    }
+}
Index: /trunk/gsdl3/extensions/gsdl-as/test-src/org/greenstone/gsdlas/util/MailerTest.java
===================================================================
--- /trunk/gsdl3/extensions/gsdl-as/test-src/org/greenstone/gsdlas/util/MailerTest.java	(revision 8888)
+++ /trunk/gsdl3/extensions/gsdl-as/test-src/org/greenstone/gsdlas/util/MailerTest.java	(revision 8888)
@@ -0,0 +1,35 @@
+/*
+ * Created on Jan 1, 2005
+ *
+ * TODO To change the template for this generated file go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+package org.greenstone.gsdlas.util;
+
+import javax.mail.MessagingException;
+
+import junit.framework.TestCase;
+
+/**
+ * @author andrea
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class MailerTest extends TestCase {
+
+    public static void main(String[] args) {
+        junit.textui.TestRunner.run(MailerTest.class);
+    }
+
+    public final void testSendMail() {
+        Mailer mailer = new Mailer("andrea@localhost", "localhost");
+        try {
+            mailer.sendMail("andrea@localhost", "test", "test message testing...\n");
+        } catch (MessagingException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+}
Index: /trunk/gsdl3/packages/gsdl-as/create-tables.sql
===================================================================
--- /trunk/gsdl3/packages/gsdl-as/create-tables.sql	(revision 8887)
+++ /trunk/gsdl3/packages/gsdl-as/create-tables.sql	(revision 8888)
@@ -30,2 +30,15 @@
 	unique (subscription,predicate)
 );
+
+create table events (
+	id int primary key auto_increment,
+	timestamp datetime not null,
+	content text not null
+);
+
+create table events_to_subs (
+	event int not null references events.id,
+	subscription int not null references subscriptions.id,
+
+	unique (event,subscription)
+);
Index: /trunk/gsdl3/packages/gsdl-as/lib/templates/events.vm
===================================================================
--- /trunk/gsdl3/packages/gsdl-as/lib/templates/events.vm	(revision 8888)
+++ /trunk/gsdl3/packages/gsdl-as/lib/templates/events.vm	(revision 8888)
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>Greenstone Digital Library Software</title>
+<link href="default.css" rel="stylesheet" type="text/css"/>
+</head>
+<body>
+<div id="content">
+<h3>Alerting Service for Greenstone</h3>
+<p>Events for subscription $subscription.name</p>
+<p class="description">$subscription</p>
+<p><a href="/alerting/service?action=showFeed&amp;subscriptionID=$subscription.id">show as feed</a></p>
+#foreach ($event in $list)
+<p>$event</p>
+#end
+<a href="/alerting">back</a> to the alerting service
+</div>
+
+</body>
+</html>
Index: /trunk/gsdl3/packages/gsdl-as/lib/templates/feed.vm
===================================================================
--- /trunk/gsdl3/packages/gsdl-as/lib/templates/feed.vm	(revision 8887)
+++ /trunk/gsdl3/packages/gsdl-as/lib/templates/feed.vm	(revision 8888)
@@ -1,13 +1,13 @@
 <?xml version="1.0"?>
-<?xml-stylesheet type="text/xsl" href="/alerting/rss2html.xsl"?>
+<!--<?xml-stylesheet type="text/xml" href="/alerting/rss2html.xml"?>-->
 <rss version="2.0">
    <channel>
-      <title>$title</title>
-      <link>TODO</link>
-      <description>TDOD</description>
+      <title>Events for Subscription $subscription.name</title>
+      <link>/alerting/service?action=showFeed&amp;subscriptionID=$subscription.id</link>
+      <description>TODO</description>
       <language>en</language>
-      <lastBuildDate>TODO</lastBuildDate>
+      <lastBuildDate>$lastBuildDate</lastBuildDate>
       <generator>Alerting Service for Greenstone</generator>
-      <webMaster>TODO</webMaster>
+      <webMaster>$webmaster</webMaster>
       
       #foreach($event in $list)
Index: /trunk/gsdl3/packages/gsdl-as/lib/templates/list.vm
===================================================================
--- /trunk/gsdl3/packages/gsdl-as/lib/templates/list.vm	(revision 8887)
+++ /trunk/gsdl3/packages/gsdl-as/lib/templates/list.vm	(revision 8888)
@@ -14,6 +14,6 @@
 	<div class="subscription">
 		<p><span class="sub_id">Subscription $sub.Id</span><br/>
-		<a href="service?action=showFeed&subscriptionID=$sub.Id">show matching events</a> | <a href="service?action=editSubscription&subscriptionID=$sub.Id">edit subscription</a> | <a href="service?action=deleteSubscription&subscriptionID=$sub.Id">delete subscription</a></p>
-		<table class="subscription">
+		<a href="service?action=showEvents&subscriptionID=$sub.Id">show matching events</a> | <a href="service?action=editSubscription&subscriptionID=$sub.Id">edit subscription</a> | <a href="service?action=deleteSubscription&subscriptionID=$sub.Id">delete subscription</a></p>
+<!--		<table class="subscription">
 			<thead>
 				<tr><th>Field</th><th>Predicate</th></tr>
@@ -28,5 +28,8 @@
 				#end
 			</tbody>
-		</table>
+		</table> // -->
+		<pre>
+		$sub
+		</pre>
 		<br/>
 	</div>
Index: /trunk/gsdl3/packages/gsdl-as/lib/templates/sub_type-details.vm
===================================================================
--- /trunk/gsdl3/packages/gsdl-as/lib/templates/sub_type-details.vm	(revision 8887)
+++ /trunk/gsdl3/packages/gsdl-as/lib/templates/sub_type-details.vm	(revision 8888)
@@ -4,5 +4,5 @@
 <html xmlns="http://www.w3.org/1999/xhtml">
 	<head>
-		<title>Andrea Schweer</title>
+		<title>Create a subscription</title>
 		<link rel="stylesheet" href="/alerting/form.css" type="text/css"/>
 	</head>
Index: /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/AlertingService.java
===================================================================
--- /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/AlertingService.java	(revision 8887)
+++ /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/AlertingService.java	(revision 8888)
@@ -10,5 +10,4 @@
 
 import java.lang.reflect.Method;
-import java.net.MalformedURLException;
 import java.net.URL;
 import java.sql.SQLException;
@@ -24,4 +23,5 @@
 import org.greenstone.gsdlas.database.DatabaseException;
 import org.greenstone.gsdlas.profiles.Predicate;
+import org.greenstone.gsdlas.profiles.Subscription;
 import org.greenstone.gsdlas.users.UserManagementException;
 import org.greenstone.gsdlas.users.UserManager;
@@ -58,4 +58,5 @@
             "deleteSubscription",
             "editSubscription",
+            "showEvents",
             "showFeed",
             "listSubscriptions",
@@ -175,7 +176,32 @@
     }
     
-    public String showFeed(Map arguments, Context context) {
-        context.put("list", ""/* TODO pass subscription list */);
+    public String showFeed(Map arguments, Context context) throws NumberFormatException, DatabaseException, SQLException {
+        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
+        if (!UserManager.getInstance().isLoggedIn(session)) {
+            session.setAttribute("next_action", "showFeed");
+            return showLoginForm(arguments, context);
+        }
+        String subscriptionID = (String) arguments.get("subscriptionID");
+        Integer subID = new Integer(subscriptionID);
+        Set events = EventStore.getInstance().getEvents(subID);
+        context.put("list", events);
+        context.put("subscription", ProfileStore.getInstance().getSubscription(subID.intValue()));
+        HttpServletResponse res = (HttpServletResponse) context.get(RESPONSE);
+        res.setContentType("text/xml");
         return "feed.vm";
+    }
+    
+    public String showEvents(Map arguments, Context context) throws NumberFormatException, DatabaseException, SQLException {
+        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
+        if (!UserManager.getInstance().isLoggedIn(session)) {
+            session.setAttribute("next_action", "showEvents");
+            return showLoginForm(arguments, context);
+        }
+        String subscriptionID = (String) arguments.get("subscriptionID");
+        Integer subID = new Integer(subscriptionID);
+        Set events = EventStore.getInstance().getEvents(subID);
+        context.put("list", events);
+        context.put("subscription", ProfileStore.getInstance().getSubscription(subID.intValue()));
+        return "events.vm";
     }
     
@@ -250,4 +276,5 @@
         for (Iterator iter = rawEvent.keySet().iterator(); iter.hasNext();) {
             String key = (String) iter.next();
+            if (key.equals("action")) continue; // we don't want this
             String[] value = (String[]) rawEvent.get(key);
             event.put(key, value[0]);
@@ -259,14 +286,19 @@
             String hostID = (String) event.get(Constants.HOST_ID_FIELD);
             gsComm = new GreenstoneCommunicator(new URL(hostID));
-        } catch (MalformedURLException e) {
-            // TODO Auto-generated catch block
+        } catch (Exception e) {
+            System.err.println("Can't communicate to Greenstone: " + e.getMessage());
             e.printStackTrace();
+        }
+        
+        Set matchedSubscriptions = ProfileStore.getInstance().filter(event, gsComm);
+        
+        try {
+            EventStore.getInstance().add(event, matchedSubscriptions);
+            Notifier.getInstance().sendNotifications(event, matchedSubscriptions);
         } catch (Exception e) {
-            // TODO Auto-generated catch block
+            System.err.println("Couldn't save events: " + e.getMessage());
             e.printStackTrace();
         }
-        Set matchedSubscriptions = ProfileStore.getInstance().filter(event, gsComm);
         System.out.println(matchedSubscriptions.size() + " matching subscriptions: " + matchedSubscriptions);
-        // TODO do something with the matched subscriptions
     }
 
@@ -322,8 +354,13 @@
      */
     private String showSubscriptionWizardPage(Map arguments, Context context, boolean create) throws Exception {
+        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
         if (create) {
             context.put(Constants.ACTION_PARAM, "createSubscription");
         } else {
+            // prefill from existing subscription
             context.put(Constants.ACTION_PARAM, "editSubscription");
+            String subscriptionID = (String) arguments.get("subscriptionID");
+            int subID = Integer.parseInt(subscriptionID);
+            prefillFromSubscription(session, subID);
         }
         if (!arguments.containsKey("current_page")) {
@@ -331,5 +368,4 @@
         }
         
-        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
         String currentPage = (String) arguments.get("current_page");
         String direction = (String) arguments.get("next_page");
@@ -412,4 +448,21 @@
 
     /**
+     * @param session
+     * @param subID
+     */
+    private void prefillFromSubscription(HttpSession session, int subID) {
+        Subscription sub = ProfileStore.getInstance().getSubscription(subID);
+        Map typeArgs = new HashMap();
+        // TODO really fill stuff
+        savePageArgsToSession("type-details", typeArgs, session);
+        Map hostArgs = new HashMap();
+        savePageArgsToSession("host", hostArgs, session);
+        Map collArgs = new HashMap();
+        savePageArgsToSession("collection", collArgs, session);
+        Map notificationArgs = new HashMap();
+        savePageArgsToSession("notification", notificationArgs, session);
+    }
+
+    /**
      * @param nextPage
      * @param session
Index: /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/EventStore.java
===================================================================
--- /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/EventStore.java	(revision 8888)
+++ /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/EventStore.java	(revision 8888)
@@ -0,0 +1,169 @@
+/*
+ * Created on Nov 23, 2004
+ * Copyright (C) Andrea Schweer, 2004
+ *
+ * This file is part of the Greenstone Alerting Service.
+ * Refer to the COPYING file in the base directory of this package
+ * for licensing information.
+ */
+package org.greenstone.gsdlas;
+
+import java.sql.*;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.*;
+import java.util.Date;
+
+import org.greenstone.gsdlas.database.DatabaseException;
+import org.greenstone.gsdlas.database.DatabaseManager;
+import org.greenstone.gsdlas.profiles.Subscription;
+
+/**
+ * @author schweer
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class EventStore {
+
+    private static EventStore instance;
+    
+    private EventStore() {
+    }
+    
+    /**
+     * @return
+     */
+    public static EventStore getInstance() {
+        if (instance == null) instance = new EventStore();
+        return instance;
+    }
+    
+    public void add(Map event, Set subscriptions) throws DatabaseException, SQLException, ParseException {
+        Connection conn = DatabaseManager.getInstance().getDatabaseConnection();
+        Statement statement = conn.createStatement();
+
+        int eventID = saveEventToDatabase(event);
+        
+        for (Iterator iter = subscriptions.iterator(); iter.hasNext();) {
+            Subscription sub = (Subscription) iter.next();
+            int subID = sub.getId();
+            String sqlString = "INSERT INTO events_to_subs (event,subscription) " +
+            		"VALUES (" + eventID + "," + subID + ");";
+            statement.executeUpdate(sqlString);
+        }
+    }
+
+    /**
+     * @param event
+     * @return
+     * @throws DatabaseException
+     * @throws SQLException
+     * @throws ParseException
+     */
+    private int saveEventToDatabase(Map event) throws DatabaseException, SQLException, ParseException {
+        String eventString = mapAsString(event);
+        String timestamp = (String) event.get("timestamp");
+        timestamp = convertToSQLDatetime(timestamp);
+        
+        Connection conn = DatabaseManager.getInstance().getDatabaseConnection();
+        Statement statement = conn.createStatement();
+        String query = "SELECT id FROM events WHERE timestamp = " + timestamp + 
+        	" AND content = '" + eventString + "';";
+        ResultSet results = statement.executeQuery(query);
+        if (results.next()) {
+            return results.getInt("id");
+        }
+        
+        String insert = "INSERT INTO events (timestamp,content) " +
+        		"VALUES (" + timestamp + ",'" + eventString + "');"; 
+        statement.executeUpdate(insert);
+        results = statement.executeQuery(query);
+        if (results.next()) {
+            return results.getInt("id");
+        }
+        throw new DatabaseException("couldn't save event to database");
+    }
+
+    /**
+     * converts a timestamp from an event to a format recognised by MySQL
+     * @param timestamp
+     * @return
+     * @throws ParseException
+     */
+    private String convertToSQLDatetime(String timestamp) throws ParseException {
+        Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z").parse(timestamp);
+        return new SimpleDateFormat("yyyyMMddHHmmss").format(date);
+    }
+
+    /**
+     * @param event
+     */
+    private String encodeSpecialChars(String string) {
+        String encodedString = string.replace("&", "&amp;");
+        encodedString = encodedString.replace("<", "&lt;");
+        encodedString = encodedString.replace(">", "&gt;");
+        encodedString = encodedString.replace("'", "&apos;");
+        encodedString = encodedString.replace("\"", "&quot;");
+        return encodedString;
+    }
+
+    /**
+     * @param subscriptionID
+     * @return
+     * @throws DatabaseException
+     * @throws SQLException
+     */
+    public Set getEvents(Integer subscriptionID) throws DatabaseException, SQLException {
+        Set result = new HashSet(); // can't use TreeSet, because we put Maps in there, and Map isn't instanceof Comparable
+        Connection conn = DatabaseManager.getInstance().getDatabaseConnection();
+        Statement statement = conn.createStatement();
+        String query = "SELECT content " +
+        		"FROM events e JOIN events_to_subs ets ON (e.id = ets.event) " +
+        		"WHERE ets.subscription = " + subscriptionID.intValue() + ";";
+        ResultSet results = statement.executeQuery(query);
+        while (results.next()) {
+            Map event = stringAsMap(results.getString("content"));
+            result.add(event);
+        }
+        return result;
+    }
+
+    private String mapAsString(Map map) {
+        StringBuffer buffer = new StringBuffer();
+        for (Iterator iter = map.keySet().iterator(); iter.hasNext();) {
+            String key = (String) iter.next();
+            String value = (String) map.get(key);
+            String encodedValue = encodeSpecialChars(value);
+            buffer.append(key);
+            buffer.append(">"); // > has been encoded to &gt; in the original String
+            buffer.append(encodedValue);
+            if (iter.hasNext()) {
+                buffer.append(">>");
+            }
+        }
+        return buffer.toString();
+    }
+    
+    private Map stringAsMap(String string) {
+        Map map = new HashMap();
+        String[] lines = string.split(">>");
+        for (int i = 0; i < lines.length; i++) {
+            String line = lines[i];
+            String[] columns = line.split(">");
+            String key = columns[0];
+            String value = null;
+            if (columns.length > 1) {
+                value = columns[1];
+            }
+            map.put(key, value);
+        }
+        return map;
+    }
+    
+    public void cleanUp() {
+        // TODO implement method 
+        // delete all events that are too old (?)
+        // delete all events that don't match any subscriptions
+    }
+}
Index: /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/Notifier.java
===================================================================
--- /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/Notifier.java	(revision 8888)
+++ /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/Notifier.java	(revision 8888)
@@ -0,0 +1,96 @@
+/*
+ * Created on Jan 1, 2005
+ * Copyright (C) 2004, 2005 Andrea Schweer
+ *
+ * This file is part of the Greenstone Alerting Service.
+ * Refer to the COPYING file in the base directory of this package
+ * for licensing information.
+ */
+package org.greenstone.gsdlas;
+
+import java.util.*;
+import java.util.Map;
+import java.util.Set;
+
+import javax.mail.MessagingException;
+
+import org.greenstone.gsdlas.profiles.Subscription;
+import org.greenstone.gsdlas.util.Mailer;
+
+/**
+ * @author andrea
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ * 
+ * Singleton
+ */
+public class Notifier {
+
+    private static Notifier instance; 
+    
+    private Notifier () {
+        // hide default constructor
+    }
+    
+    /**
+     * @return
+     */
+    public static Notifier getInstance() {
+        if (instance == null) instance = new Notifier();
+        return instance;
+    }
+
+    /**
+     * @param event
+     * @param matchedSubscriptions
+     */
+    public void sendNotifications(Map event, Set matchedSubscriptions) {
+        // TODO read smtp host and sender from configuration file
+        Mailer mailer = new Mailer(System.getProperty("user.name") + "@localhost", "localhost");
+        String eventText = eventToEmail(event);
+        String subject = "Event for your subscription";
+        
+        for (Iterator iter = matchedSubscriptions.iterator(); iter.hasNext();) {
+            Subscription sub = (Subscription) iter.next();
+            if (!sub.wantsEMailNotification()) continue;
+
+            StringBuffer message = new StringBuffer();
+            // TODO formatting of notification email via velocity?
+            message.append("You've created a subscription at the Greenstone Alerting Service running on " +
+            		"localhost:\n\n");
+            message.append(sub.toString());
+            message.append("\n");
+            message.append("An event has occurred that matches this subscription:\n\n");
+            message.append(eventText);
+            message.append("\n");
+            message.append("Go to http://localhost:8080/alerting/service?action=listSubscriptions to " +
+            		"change your subscriptions, or to http://localhost:8080/alerting/service?action=showEvents&subscriptionID=" + 
+            		sub.getId() + " to see all events that match the subscription.\n");
+            
+            String to = sub.getMailAddress();
+            
+            try {
+                mailer.sendMail(to, subject, message.toString());
+            } catch (MessagingException e) {
+                System.err.println("Couldn't notify for subscription " + sub.getId() + ": " + e.getMessage());
+                e.printStackTrace();
+            }
+        }
+    }
+
+    /**
+     * @param event
+     * @return
+     */
+    private String eventToEmail(Map event) {
+        StringBuffer message = new StringBuffer();
+        for (Iterator iter = event.keySet().iterator(); iter.hasNext();) {
+            String key = (String) iter.next();
+            String value = (String) event.get(key);
+            message.append(key + ": " + value + "\n");
+        }
+        return message.toString();
+    }
+
+}
Index: /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/ProfileStore.java
===================================================================
--- /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/ProfileStore.java	(revision 8887)
+++ /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/ProfileStore.java	(revision 8888)
@@ -405,4 +405,12 @@
         }
     }
+
+    /**
+     * @param i
+     * @return
+     */
+    public Subscription getSubscription(int id) {
+        return (Subscription) subscriptions.get(new Integer(id));
+    }
     
 }
Index: /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/database/DatabaseManager.java
===================================================================
--- /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/database/DatabaseManager.java	(revision 8887)
+++ /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/database/DatabaseManager.java	(revision 8888)
@@ -9,4 +9,5 @@
 package org.greenstone.gsdlas.database;
 
+import java.sql.*;
 import java.sql.Connection;
 import java.sql.DriverManager;
@@ -42,4 +43,8 @@
                 database = DriverManager.getConnection(connectionString);
             } catch (Exception e) {
+                if (e instanceof SQLException) {
+                    SQLException sqle = (SQLException) e;
+                    System.err.println("SQLState: " + sqle.getSQLState());
+                }
                 throw new DatabaseException("Could not establish connection to database (" + connectionString + ")", e);
             }
Index: /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/profiles/PredicateFactory.java
===================================================================
--- /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/profiles/PredicateFactory.java	(revision 8887)
+++ /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/profiles/PredicateFactory.java	(revision 8888)
@@ -1,5 +1,5 @@
 /*
  * Created on Nov 1, 2004
- * Copyright (C) Andrea Schweer, 2004
+ * Copyright (C) 2004, 2005 Andrea Schweer
  *
  * This file is part of the Greenstone Alerting Service.
Index: /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/profiles/QueryPredicate.java
===================================================================
--- /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/profiles/QueryPredicate.java	(revision 8887)
+++ /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/profiles/QueryPredicate.java	(revision 8888)
@@ -31,3 +31,7 @@
         return true;
     }
+    
+    public String toString() {
+        return field + " matches " + value;
+    }
 }
Index: /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/profiles/Subscription.java
===================================================================
--- /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/profiles/Subscription.java	(revision 8887)
+++ /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/profiles/Subscription.java	(revision 8888)
@@ -188,5 +188,29 @@
     
     public String toString() {
-        return "Subscription id " + id + ": " + map;
+        StringBuffer result = new StringBuffer("Subscription ");
+        result.append(name != null ? name : "(no name) ");
+        result.append(" (id ");
+        result.append(id);
+        result.append("):\n");
+        for (Iterator iter = map.keySet().iterator(); iter.hasNext();) {
+            String key = (String) iter.next();
+            Object value = map.get(key);
+            if (value instanceof Predicate) {
+                Predicate pred = (Predicate) value;
+                result.append(pred);
+            }
+            if (value instanceof List) {
+                List list = (List) value;
+                for (Iterator iterator = list.iterator(); iterator.hasNext();) {
+                    Predicate pred = (Predicate) iterator.next();
+                    result.append(pred);
+                    if (iterator.hasNext()) {
+                        result.append(" or ");
+                    }
+                }
+            }
+            result.append("\n");
+        }
+        return result.toString();
     }
     
@@ -255,4 +279,22 @@
         return id;
     }
+
+    /**
+     * @return
+     */
+    public boolean wantsEMailNotification() {
+        return email != null && email.length() > 1;
+    }
+
+    /**
+     * @return
+     */
+    public String getMailAddress() {
+        return email;
+    }
+    
+    public String getName() {
+        return name;
+    }
     
 }
Index: /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/profiles/SubstringMatchPredicate.java
===================================================================
--- /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/profiles/SubstringMatchPredicate.java	(revision 8887)
+++ /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/profiles/SubstringMatchPredicate.java	(revision 8888)
@@ -33,3 +33,7 @@
     }
 
+    public String toString() {
+        return field + " contains " + value;
+    }
+    
 }
Index: /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/util/Mailer.java
===================================================================
--- /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/util/Mailer.java	(revision 8888)
+++ /trunk/gsdl3/packages/gsdl-as/src/org/greenstone/gsdlas/util/Mailer.java	(revision 8888)
@@ -0,0 +1,58 @@
+/*
+ * Created on Jan 1, 2005
+ * Copyright (C) 2004, 2005 Andrea Schweer
+ *
+ * This file is part of the Greenstone Alerting Service.
+ * Refer to the COPYING file in the base directory of this package
+ * for licensing information.
+ */
+package org.greenstone.gsdlas.util;
+
+import java.util.Properties;
+
+import javax.mail.*;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+
+/**
+ * @author schweer
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class Mailer {
+    
+    private String fromAddress;
+    private String mailHost;
+    
+    public Mailer (String from, String host) {
+        fromAddress = from;
+        mailHost = host;
+    }
+    
+    public void sendMail(String to, String subject, String message) throws MessagingException {
+        // Set the host smtp address
+        Properties props = new Properties();
+        props.put("mail.smtp.host", mailHost);
+        
+        // create some properties and get the default Session
+        //Session session = Session.getDefaultInstance(props, null);
+        Session session = Session.getInstance(props);
+        
+        // create a message
+        Message msg = new MimeMessage(session);
+        
+        // set the from and to address
+        InternetAddress addressFrom = new InternetAddress(fromAddress);
+        msg.setFrom(addressFrom);
+        
+        InternetAddress[] addressTo = new InternetAddress[1];
+        addressTo[0] = new InternetAddress(to);
+        msg.setRecipients(Message.RecipientType.TO, addressTo);
+        
+        // Setting the Subject and Content Type
+        msg.setSubject(subject);
+        msg.setContent(message, "text/plain");
+        Transport.send(msg);
+    }
+}
Index: /trunk/gsdl3/packages/gsdl-as/test-src/org/greenstone/gsdlas/util/MailerTest.java
===================================================================
--- /trunk/gsdl3/packages/gsdl-as/test-src/org/greenstone/gsdlas/util/MailerTest.java	(revision 8888)
+++ /trunk/gsdl3/packages/gsdl-as/test-src/org/greenstone/gsdlas/util/MailerTest.java	(revision 8888)
@@ -0,0 +1,35 @@
+/*
+ * Created on Jan 1, 2005
+ *
+ * TODO To change the template for this generated file go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+package org.greenstone.gsdlas.util;
+
+import javax.mail.MessagingException;
+
+import junit.framework.TestCase;
+
+/**
+ * @author andrea
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class MailerTest extends TestCase {
+
+    public static void main(String[] args) {
+        junit.textui.TestRunner.run(MailerTest.class);
+    }
+
+    public final void testSendMail() {
+        Mailer mailer = new Mailer("andrea@localhost", "localhost");
+        try {
+            mailer.sendMail("andrea@localhost", "test", "test message testing...\n");
+        } catch (MessagingException e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+}
