Index: gs3-extensions/gsdl-as/trunk/.cvsignore
===================================================================
--- gs3-extensions/gsdl-as/trunk/.cvsignore	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/.cvsignore	(revision 21052)
@@ -0,0 +1,3 @@
+build
+dist
+test-classes
Index: gs3-extensions/gsdl-as/trunk/COPYING
===================================================================
--- gs3-extensions/gsdl-as/trunk/COPYING	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/COPYING	(revision 21052)
@@ -0,0 +1,353 @@
+The files in this directory and all its subdirectories belong to the
+Greenstone Alerting Service. These files, with the exemption of the JAR files
+in the lib subdirectory, are Copyright (C) 2004, 2005 by Andrea Schweer
+(schweer@cs.waikato.ac.nz) and are licensed under the GNU General Public
+License Version 2 (see below). For the JAR files in the lib subdirectory, see
+the license information inside each JAR file or in the lib subdirectory.
+
+===============================================================================
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
Index: gs3-extensions/gsdl-as/trunk/build.xml
===================================================================
--- gs3-extensions/gsdl-as/trunk/build.xml	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/build.xml	(revision 21052)
@@ -0,0 +1,517 @@
+<!--
+     General purpose build script for web applications and web services,
+     including enhanced support for deploying directly to a Tomcat 4
+     based server.
+
+     This build script assumes that the source code of your web application
+     is organized into the following subdirectories underneath the source
+     code directory from which you execute the build script:
+
+        docs                 Static documentation files to be copied to
+                             the "docs" subdirectory of your distribution.
+
+        src                  Java source code (and associated resource files)
+                             to be compiled to the "WEB-INF/classes"
+                             subdirectory of your web applicaiton.
+
+        web                  Static HTML, JSP, and other content (such as
+                             image files), including the WEB-INF subdirectory
+                             and its configuration file contents.
+
+     $Id$
+-->
+
+
+<!-- A "project" describes a set of targets that may be requested
+     when Ant is executed.  The "default" attribute defines the
+     target which is executed if no specific target is requested,
+     and the "basedir" attribute defines the current working directory
+     from which Ant executes the requested task.  This is normally
+     set to the current working directory.
+-->
+
+<project name="GSDL Alerting Service" default="compile" basedir=".">
+
+
+
+<!-- ===================== Property Definitions =========================== -->
+
+
+<!--
+
+  Each of the following properties are used in the build script.
+  Values for these properties are set by the first place they are
+  defined, from the following list:
+
+  * Definitions on the "ant" command line (ant -Dfoo=bar compile).
+
+  * Definitions from a "build.properties" file in the top level
+    source directory of this application.
+
+  * Definitions from a "build.properties" file in the developer's
+    home directory.
+
+  * Default definitions in this build.xml file.
+
+  You will note below that property values can be composed based on the
+  contents of previously defined properties.  This is a powerful technique
+  that helps you minimize the number of changes required when your development
+  environment is modified.  Note that property composition is allowed within
+  "build.properties" files as well as in the "build.xml" script.
+
+-->
+
+  <property file="build.properties"/>
+  <property file="${user.home}/build.properties"/>
+
+
+<!-- ==================== File and Directory Names ======================== -->
+
+
+<!--
+
+  These properties generally define file and directory names (or paths) that
+  affect where the build process stores its outputs.
+
+  app.name             Base name of this application, used to
+                       construct filenames and directories.
+                       Defaults to "myapp".
+
+  app.path             Context path to which this application should be
+                       deployed (defaults to "/" plus the value of the
+                       "app.name" property).
+
+  app.version          Version number of this iteration of the application.
+
+  build.home           The directory into which the "prepare" and
+                       "compile" targets will generate their output.
+                       Defaults to "build".
+
+  catalina.home        The directory in which you have installed
+                       a binary distribution of Tomcat 4.  This will
+                       be used by the "deploy" target.
+
+  dist.home            The name of the base directory in which
+                       distribution files are created.
+                       Defaults to "dist".
+
+  manager.password     The login password of a user that is assigned the
+                       "manager" role (so that he or she can execute
+                       commands via the "/manager" web application)
+
+  manager.url          The URL of the "/manager" web application on the
+                       Tomcat installation to which we will deploy web
+                       applications and web services.
+
+  manager.username     The login username of a user that is assigned the
+                       "manager" role (so that he or she can execute
+                       commands via the "/manager" web application)
+
+-->
+
+  <property name="app.name"      value="gsdl-as"/>
+  <property name="app.path"      value="/alerting"/>
+  <property name="app.version"   value="0.1-dev"/>
+  <property name="build.home"    value="${basedir}/build"/>
+  <property name="catalina.home" value="../../comms/jakarta/tomcat/"/>
+  <property name="dist.home"     value="${basedir}/dist"/>
+  <property name="docs.home"     value="${basedir}/docs"/>
+  <property name="docs.link"     value="http://java.sun.com/j2se/1.4.2/docs/api/"/>
+  <property name="manager.url"   value="http://localhost:8080/manager"/>
+  <property name="manager.username" value="admin"/>
+  <property name="manager.password" value="admin"/>
+  <property name="src.home"      value="${basedir}/src"/>
+  <property name="web.home"      value="${basedir}/web"/>
+
+
+<!-- ================== Custom Ant Task Definitions ======================= -->
+
+
+<!--
+
+  These properties define custom tasks for the Ant build tool that interact
+  with the "/manager" web application installed with Tomcat 4.  Before they
+  can be successfully utilized, you must perform the following steps:
+
+  - Copy the file "server/lib/catalina-ant.jar" from your Tomcat 4
+    installation into the "lib" directory of your Ant installation.
+
+  - Create a "build.properties" file in your application's top-level
+    source directory (or your user login home directory) that defines
+    appropriate values for the "manager.password", "manager.url", and
+    "manager.username" properties described above.
+
+  For more information about the Manager web application, and the functionality
+  of these tasks, see <http://localhost:8080/tomcat-docs/manager-howto.html>.
+
+-->
+
+  <taskdef name="install" classname="org.apache.catalina.ant.InstallTask"
+    classpath="${catalina.home}/server/lib/catalina-ant.jar"/>
+  <taskdef name="list"    classname="org.apache.catalina.ant.ListTask"
+    classpath="${catalina.home}/server/lib/catalina-ant.jar"/>
+  <taskdef name="reload"  classname="org.apache.catalina.ant.ReloadTask"
+    classpath="${catalina.home}/server/lib/catalina-ant.jar"/>
+  <taskdef name="remove"  classname="org.apache.catalina.ant.RemoveTask"
+    classpath="${catalina.home}/server/lib/catalina-ant.jar"/>
+
+
+<!--  ==================== Compilation Control Options ==================== -->
+
+<!--
+
+  These properties control option settings on the Javac compiler when it
+  is invoked using the <javac> task.
+
+  compile.debug        Should compilation include the debug option?
+
+  compile.deprecation  Should compilation include the deprecation option?
+
+  compile.optimize     Should compilation include the optimize option?
+
+-->
+
+  <property name="compile.debug"       value="true"/>
+  <property name="compile.deprecation" value="false"/>
+  <property name="compile.optimize"    value="true"/>
+
+
+
+<!-- ==================== External Dependencies =========================== -->
+
+
+<!--
+
+  Use property values to define the locations of external JAR files on which
+  your application will depend.  In general, these values will be used for
+  two purposes:
+  * Inclusion on the classpath that is passed to the Javac compiler
+  * Being copied into the "/WEB-INF/lib" directory during execution
+    of the "deploy" target.
+
+  Because we will automatically include all of the Java classes that Tomcat 4
+  exposes to web applications, we will not need to explicitly list any of those
+  dependencies.  You only need to worry about external dependencies for JAR
+  files that you are going to include inside your "/WEB-INF/lib" directory.
+
+-->
+
+<!-- Dummy external dependency -->
+<!--
+  <property name="foo.jar"
+           value="/path/to/foo.jar"/>
+-->
+
+
+<!-- ==================== Compilation Classpath =========================== -->
+
+<!--
+
+  Rather than relying on the CLASSPATH environment variable, Ant includes
+  features that makes it easy to dynamically construct the classpath you
+  need for each compilation.  The example below constructs the compile
+  classpath to include the servlet.jar file, as well as the other components
+  that Tomcat makes available to web applications automatically, plus anything
+  that you explicitly added.
+
+-->
+
+  <path id="compile.classpath">
+
+    <!-- Include all JAR files that will be included in /WEB-INF/lib -->
+    <!-- *** CUSTOMIZE HERE AS REQUIRED BY YOUR APPLICATION *** -->
+    <fileset dir="lib">
+    	<include name="*.jar"/>
+    </fileset>
+    <!-- include greenstone files -->
+    <fileset dir="../../web/WEB-INF/lib/">
+      <include name="*.jar"/>
+    </fileset>
+    <!-- Include all elements that Tomcat exposes to applications -->
+    <pathelement location="${catalina.home}/common/classes"/>
+    <fileset dir="${catalina.home}/common/endorsed">
+      <include name="*.jar"/>
+    </fileset>
+    <fileset dir="${catalina.home}/common/lib">
+      <include name="*.jar"/>
+    </fileset>
+    <pathelement location="${catalina.home}/shared/classes"/>
+    <fileset dir="${catalina.home}/shared/lib">
+      <include name="*.jar"/>
+    </fileset>
+
+  </path>
+
+
+
+<!-- ==================== All Target ====================================== -->
+
+<!--
+
+  The "all" target is a shortcut for running the "clean" target followed
+  by the "compile" target, to force a complete recompile.
+
+-->
+
+  <target name="all" depends="clean,compile"
+   description="Clean build and dist directories, then compile"/>
+
+
+
+<!-- ==================== Clean Target ==================================== -->
+
+<!--
+
+  The "clean" target deletes any previous "build" and "dist" directory,
+  so that you can be ensured the application can be built from scratch.
+
+-->
+
+  <target name="clean"
+   description="Delete old build and dist directories">
+    <delete dir="${build.home}"/>
+    <delete dir="${dist.home}"/>
+  </target>
+
+
+
+<!-- ==================== Compile Target ================================== -->
+
+<!--
+
+  The "compile" target transforms source files (from your "src" directory)
+  into object files in the appropriate location in the build directory.
+  This example assumes that you will be including your classes in an
+  unpacked directory hierarchy under "/WEB-INF/classes".
+
+-->
+
+  <target name="compile" depends="prepare"
+   description="Compile Java sources">
+
+    <!-- Compile Java classes as necessary -->
+    <mkdir    dir="${build.home}/WEB-INF/classes"/>
+    <javac srcdir="${src.home}"
+          destdir="${build.home}/WEB-INF/classes"
+            debug="${compile.debug}"
+      deprecation="${compile.deprecation}"
+         optimize="${compile.optimize}">
+        <classpath refid="compile.classpath"/>
+    </javac>
+
+    <!-- Copy application resources -->
+    <copy  todir="${build.home}/WEB-INF/classes">
+      <fileset dir="${src.home}" excludes="**/*.java"/>
+    </copy>
+
+  </target>
+
+
+
+<!-- ==================== Dist Target ===================================== -->
+
+
+<!--
+
+  The "dist" target creates a binary distribution of your application
+  in a directory structure ready to be archived in a tar.gz or zip file.
+  Note that this target depends on two others:
+
+  * "compile" so that the entire web application (including external
+    dependencies) will have been assembled
+
+  * "javadoc" so that the application Javadocs will have been created
+
+-->
+
+  <target name="dist" depends="compile,javadoc"
+   description="Create binary distribution">
+
+    <!-- Copy documentation subdirectories -->
+    <mkdir   dir="${dist.home}/docs"/>
+    <copy    todir="${dist.home}/docs">
+      <fileset dir="${docs.home}"/>
+    </copy>
+
+    <!-- Create application JAR file -->
+    <jar jarfile="${dist.home}/${app.name}-${app.version}.war"
+         basedir="${build.home}"/>
+
+    <!-- Copy additional files to ${dist.home} as necessary -->
+
+  </target>
+
+
+
+<!-- ==================== Install Target ================================== -->
+
+<!--
+
+  The "install" target tells the specified Tomcat 4 installation to dynamically
+  install this web application and make it available for execution.  It does
+  *not* cause the existence of this web application to be remembered across
+  Tomcat restarts; if you restart the server, you will need to re-install all
+  this web application.
+
+  If you have already installed this application, and simply want Tomcat to
+  recognize that you have updated Java classes (or the web.xml file), use the
+  "reload" target instead.
+
+  NOTE:  This target will only succeed if it is run from the same server that
+  Tomcat is running on.
+
+  NOTE:  This is the logical opposite of the "remove" target.
+
+-->
+
+  <target name="install" depends="compile"
+   description="Install application to servlet container">
+
+    <install url="${manager.url}"
+        username="${manager.username}"
+        password="${manager.password}"
+            path="${app.path}"
+             war="file://${build.home}"/>
+
+  </target>
+
+
+<!-- ==================== Javadoc Target ================================== -->
+
+<!--
+
+  The "javadoc" target creates Javadoc API documentation for the Java
+  classes included in your application.  Normally, this is only required
+  when preparing a distribution release, but is available as a separate
+  target in case the developer wants to create Javadocs independently.
+
+-->
+
+  <target name="javadoc" depends="compile"
+   description="Create Javadoc API documentation">
+
+    <mkdir          dir="${dist.home}/docs/api"/>
+    <javadoc sourcepath="${src.home}"
+                destdir="${dist.home}/docs/api"
+    			   link="${docs.link}"
+    	        private="true"
+           packagenames="*">
+      <classpath refid="compile.classpath"/>
+    </javadoc>
+
+  </target>
+
+
+
+<!-- ====================== List Target =================================== -->
+
+<!--
+
+  The "list" target asks the specified Tomcat 4 installation to list the
+  currently running web applications, either loaded at startup time or
+  installed dynamically.  It is useful to determine whether or not the
+  application you are currently developing has been installed.
+
+-->
+
+  <target name="list"
+   description="List installed applications on servlet container">
+
+    <list    url="${manager.url}"
+        username="${manager.username}"
+        password="${manager.password}"/>
+
+  </target>
+
+
+<!-- ==================== Prepare Target ================================== -->
+
+<!--
+
+  The "prepare" target is used to create the "build" destination directory,
+  and copy the static contents of your web application to it.  If you need
+  to copy static files from external dependencies, you can customize the
+  contents of this task.
+
+  Normally, this task is executed indirectly when needed.
+
+-->
+
+  <target name="prepare">
+
+    <!-- Create build directories as needed -->
+    <mkdir  dir="${build.home}"/>
+    <mkdir  dir="${build.home}/WEB-INF"/>
+    <mkdir  dir="${build.home}/WEB-INF/classes"/>
+
+
+    <!-- Copy static content of this web application -->
+    <copy todir="${build.home}">
+      <fileset dir="${web.home}"/>
+    </copy>
+
+    <jar destfile="lib/templates.jar" basedir="lib/templates" includes="*.vm"/>
+
+    <!-- Copy external dependencies as required -->
+    <!-- *** CUSTOMIZE HERE AS REQUIRED BY YOUR APPLICATION *** -->
+    <mkdir  dir="${build.home}/WEB-INF/lib"/>
+    <copy todir="${build.home}/WEB-INF/lib">
+        <fileset dir="lib" includes="*.jar"/>
+    </copy>
+
+    <!-- Copy static files from external dependencies as needed -->
+    <!-- *** CUSTOMIZE HERE AS REQUIRED BY YOUR APPLICATION *** -->
+
+  </target>
+
+
+<!-- ==================== Reload Target =================================== -->
+
+<!--
+
+  The "reload" signals the specified application Tomcat 4 to shut itself down
+  and reload. This can be useful when the web application context is not
+  reloadable and you have updated classes or property files in the
+  /WEB-INF/classes directory or when you have added or updated jar files in the
+  /WEB-INF/lib directory.
+
+  NOTE: The /WEB-INF/web.xml web application configuration file is not reread
+  on a reload. If you have made changes to your web.xml file you must stop
+  then start the web application. 
+
+-->
+
+  <target name="reload" depends="compile"
+   description="Reload application on servlet container">
+
+    <reload url="${manager.url}"
+       username="${manager.username}"
+       password="${manager.password}"
+           path="${app.path}"/>
+
+  </target>
+
+
+<!-- ==================== Remove Target =================================== -->
+
+<!--
+
+  The "remove" target tells the specified Tomcat 4 installation to dynamically
+  remove this web application from service.
+
+  NOTE:  This is the logical opposite of the "install" target.
+
+-->
+
+  <target name="remove"
+   description="Remove application on servlet container">
+
+    <remove url="${manager.url}"
+       username="${manager.username}"
+       password="${manager.password}"
+           path="${app.path}"/>
+
+  </target>
+
+
+</project>
Index: gs3-extensions/gsdl-as/trunk/create-tables.sql
===================================================================
--- gs3-extensions/gsdl-as/trunk/create-tables.sql	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/create-tables.sql	(revision 21052)
@@ -0,0 +1,44 @@
+create table users (
+	username varchar(128) primary key, 
+	password text not null
+);
+
+create table subscriptions (
+	id int primary key auto_increment, 
+	name varchar(255), 
+	email text, 
+	rss bool not null default 0, 
+	page bool not null default 0, 
+	user varchar(128) not null references users.username, 
+	
+	unique(user,name)
+);
+
+create table predicates (
+	id int primary key auto_increment, 
+	field varchar(128) not null, 
+	value varchar(255), 
+	type enum("org.greenstone.gsdlas.profiles.EqualsPredicate", "org.greenstone.gsdlas.profiles.SubstringMatchPredicate", "org.greenstone.gsdlas.profiles.QueryPredicate") not null,
+	
+	unique(field,type,value)
+);
+
+create table subs_to_predicates (
+	subscription int not null references subscriptions.id, 
+	predicate int not null references predicates.id, 
+	
+	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: gs3-extensions/gsdl-as/trunk/descriptor.xml
===================================================================
--- gs3-extensions/gsdl-as/trunk/descriptor.xml	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/descriptor.xml	(revision 21052)
@@ -0,0 +1,7 @@
+<isd:service
+  xmlns:isd="http://xml.apache.org/xml-soap/deployment"
+  id="alerting" checkMustUnderstands="false">
+  <isd:provider type="java" scope="Session" methods="receiveEvent">
+    <isd:java class="org.greenstone.gsdlas.AlertingService" static="false"/>
+  </isd:provider>
+</isd:service>
Index: gs3-extensions/gsdl-as/trunk/lib/.cvsignore
===================================================================
--- gs3-extensions/gsdl-as/trunk/lib/.cvsignore	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/lib/.cvsignore	(revision 21052)
@@ -0,0 +1,1 @@
+templates.jar
Index: gs3-extensions/gsdl-as/trunk/lib/templates/error.vm
===================================================================
--- gs3-extensions/gsdl-as/trunk/lib/templates/error.vm	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/lib/templates/error.vm	(revision 21052)
@@ -0,0 +1,19 @@
+<?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>$title</p>
+			<p class="error">$message</p>
+			<p class="details">$details</p>
+			<a href="/alerting">back</a> to the alerting service
+		</div>
+
+	</body>
+</html>
Index: gs3-extensions/gsdl-as/trunk/lib/templates/events.vm
===================================================================
--- gs3-extensions/gsdl-as/trunk/lib/templates/events.vm	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/lib/templates/events.vm	(revision 21052)
@@ -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: gs3-extensions/gsdl-as/trunk/lib/templates/feed.vm
===================================================================
--- gs3-extensions/gsdl-as/trunk/lib/templates/feed.vm	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/lib/templates/feed.vm	(revision 21052)
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<!--<?xml-stylesheet type="text/xml" href="/alerting/rss2html.xml"?>-->
+<rss version="2.0">
+   <channel>
+      <title>Events for Subscription $subscription.name</title>
+      <link>/alerting/service?action=showFeed&amp;subscriptionID=$subscription.id</link>
+      <description>TODO</description>
+      <language>en</language>
+      <lastBuildDate>$lastBuildDate</lastBuildDate>
+      <generator>Alerting Service for Greenstone</generator>
+      <webMaster>$webmaster</webMaster>
+      
+      #foreach($event in $list)
+      <item>
+         <title>$event.document_title</title>
+         <link>$event.document_url</link>
+         <description>some description</description>
+         <pubDate>$event.timestamp</pubDate>
+         <guid>$event.document_url</guid>
+         <category>$event.type</category>
+      </item>
+      #end
+   </channel>
+</rss>
Index: gs3-extensions/gsdl-as/trunk/lib/templates/general.vm
===================================================================
--- gs3-extensions/gsdl-as/trunk/lib/templates/general.vm	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/lib/templates/general.vm	(revision 21052)
@@ -0,0 +1,18 @@
+<?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>$title</p>
+<p class="message">$message</p>
+<a href="/alerting">back</a> to the alerting service
+</div>
+
+</body>
+</html>
Index: gs3-extensions/gsdl-as/trunk/lib/templates/list.vm
===================================================================
--- gs3-extensions/gsdl-as/trunk/lib/templates/list.vm	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/lib/templates/list.vm	(revision 21052)
@@ -0,0 +1,41 @@
+<?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="list.css" rel="stylesheet" type="text/css"/>
+</head>
+<body>
+<div id="content">
+<h3>Alerting Service for Greenstone</h3>
+<p>$title</p>
+#foreach($sub in $list)
+	<div class="subscription">
+		<p><span class="sub_id">Subscription $sub.Id</span><br/>
+		<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>
+			</thead>
+			<tfoot><tr><th/><th/></tr></tfoot>
+			<tbody>
+				#foreach($pred in $sub.Predicates)
+					<tr>
+						<td>$pred.Field</td>
+						<td>$pred.Value</td>
+					</tr>
+				#end
+			</tbody>
+		</table> // -->
+		<pre>
+		$sub
+		</pre>
+		<br/>
+	</div>
+#end
+<a href="/alerting">back</a> to the alerting service
+</div>
+
+</body>
+</html>
Index: gs3-extensions/gsdl-as/trunk/lib/templates/login.vm
===================================================================
--- gs3-extensions/gsdl-as/trunk/lib/templates/login.vm	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/lib/templates/login.vm	(revision 21052)
@@ -0,0 +1,35 @@
+<?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="/alerting/form.css" rel="stylesheet" type="text/css"/>
+</head>
+<body>
+<div id="content">
+<h3>Alerting Service for Greenstone</h3>
+<p>Login</p>
+#if ($error) 
+<div class="error">
+An error occured:<br/>
+$errormessage
+</div>
+#end
+<form action="/alerting/service?action=login" method="post"/>
+<table>
+<tr>
+<th><label for="username">username</label></th><td><input type="text" name="username" id="username"/></td>
+</tr>
+<tr>
+<th><label for="password">password</label></th><td><input type="password" name="password" id="password"/></td>
+</tr>
+</table>
+<br/>
+<input type="submit" value="login"/>
+</form>
+<div class="description">Don't have an account yet? <a href="/alerting/service?action=showRegistrationForm">Register</a> now!</a></div>
+</div>
+
+</body>
+</html>
Index: gs3-extensions/gsdl-as/trunk/lib/templates/register.vm
===================================================================
--- gs3-extensions/gsdl-as/trunk/lib/templates/register.vm	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/lib/templates/register.vm	(revision 21052)
@@ -0,0 +1,37 @@
+<?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="/alerting/form.css" rel="stylesheet" type="text/css"/>
+</head>
+<body>
+<div id="content">
+<h3>Alerting Service for Greenstone</h3>
+<p>Register</p>
+#if ($error)
+<div class="error">
+An error occured:<br/>
+$errormessage
+</div>
+#end
+<form action="/alerting/service?action=register" method="post"/>
+<table>
+<tr>
+<th><label for="username">username</label></th><td><input type="text" name="username" id="username"/></td>
+</tr>
+<tr>
+<th><label for="password">password</label></th><td><input type="password" name="password" id="password"/></td>
+</tr>
+<tr>
+<th><label for="password2">repeat password</label></th><td><input type="password" name="password2" id="password2"/></td>
+</tr>
+</table>
+<br/>
+<input type="submit" value="create account"/>
+</form>
+</div>
+
+</body>
+</html>
Index: gs3-extensions/gsdl-as/trunk/lib/templates/sub_collection.vm
===================================================================
--- gs3-extensions/gsdl-as/trunk/lib/templates/sub_collection.vm	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/lib/templates/sub_collection.vm	(revision 21052)
@@ -0,0 +1,76 @@
+<?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>Andrea Schweer</title>
+		<link rel="stylesheet" href="/alerting/form.css" type="text/css"/>
+	</head>
+	<body>
+		<div id="content">
+			<h3>Create a subscription (3/4)</h3>
+			<table id="progress">
+			<tr>
+			<td class="completed">type/details</td><td class="completed">host</td><td class="current">collection</td><td>notification</td>
+			</tr>
+			</table>
+			<p>Choose the collection(s) to monitor</p>
+			<form action="/alerting/service?action=$action" method="post">
+				<div><h4>Your subscription so far</h4>
+				<table class="subscription">
+				#set($evenodd = "odd")
+				#foreach($key in $preview.keySet())
+					#if(!($key == ""))
+						<tr class="$evenodd">
+							<th>$key</th>
+							<td>$preview.get($key)</td>
+						</tr>
+					#end
+					#if ($evenodd == "odd") #set($evenodd = "even") #else #set($evenodd = "odd") #end
+				#end
+				</table>
+				</div>
+				<div>
+					<h4>Which collection(s) to monitor</h4>
+					<div class="explanation">
+					You have two ways of choosing the collection(s) to monitor: 
+					<ol>
+					<li>You can choose one or more collections from the list below. Only events from the collections(s) you have chosen will be sent to you<br/>
+					<select multiple="multiple" name="hostCollectionID" size="7" style="margin:5px 0px;">
+						<option value="">any collection on any host</option>
+						#foreach($host in $hostnames)
+						<optgroup label="$host">
+							#foreach($collection in $collectionnames.get($host))
+							<option>$host|$collection</option>
+							#end
+							<option selected="selected" value="$host">any collection on host $host</option>
+						</optgroup>
+					#end
+					</select></li>
+					<li>You can enter a query in the field below. Only events coming from a collection whose name matches this query will be sent to you.<br/>
+					<table style="margin:5px 0px;">
+						<tr class="odd">
+							<th>collection name</th>
+							<td width="25%">contains <img class="help" alt="help" src="images/help.png" longdesc="substring" title="substring"/>
+							</td>
+							<td>
+								<input type="text" name="collection_query" style="width:80%;" value="$!prefill.collection_query"/>
+							</td>
+						</tr>
+						</table>
+						If you enter a query, the selection made in the list above will be ignored.
+						<!-- collection description? creator? language? -->
+						</li>
+					</ol>
+					If you just click &quot;continue&quot;, events coming from <em>any collection</em> will be sent to you.
+					</div>
+				</div>
+				<p class="explanation">
+				In the last step, you will be able to configure the the way you will be notified of events matching your subscription.
+				</p>
+				<input type="hidden" name="current_page" value="collection"/>
+				<input type="submit" name="next_page" value="back"/><input type="submit" name="next_page" value="next"/><input type="submit" name="next_page" value="finish"/>
+			</form>
+		</div>
+	</body>
+</html>
Index: gs3-extensions/gsdl-as/trunk/lib/templates/sub_host.vm
===================================================================
--- gs3-extensions/gsdl-as/trunk/lib/templates/sub_host.vm	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/lib/templates/sub_host.vm	(revision 21052)
@@ -0,0 +1,71 @@
+<?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>Andrea Schweer</title>
+		<link rel="stylesheet" href="/alerting/form.css" type="text/css"/>
+	</head>
+	<body>
+		<div id="content">
+			<h3>Create a subscription (2/4)</h3>
+			<table id="progress">
+			<tr>
+			<td class="completed">type/details</td><td  class="current">host</td><td>collection</td><td>notification</td>
+			</tr>
+			</table>
+			<p>Choose the host(s) to monitor</p>
+			<form action="/alerting/service?action=$action" method="post">
+				<div><h4>Your subscription so far</h4>
+				<table class="subscription">
+				#set($evenodd = "odd")
+				#foreach($key in $preview.keySet())
+					#if(!($key == ""))
+						<tr class="$evenodd">
+							<th>$key</th>
+							<td>$preview.get($key)</td>
+						</tr>
+					#end
+					#if ($evenodd == "odd") #set($evenodd = "even") #else #set($evenodd = "odd") #end
+				#end
+				</table>
+				</div>
+				<div>
+					<h4>Which host(s) to monitor</h4>
+					<div class="explanation">
+					You have two ways of choosing the host(s) to monitor: 
+					<ol>
+					<li>You can choose one or more hosts from the list below. Only events from the host(s) you have chosen will be sent to you.<br/>
+					<select multiple="multiple" name="hostID" size="3" style="margin:5px 0px;">
+					#set($firstoption = true);
+					#foreach($host in $hostnames)
+					<option #if($firstoption) selected="selected" #set($firstoption = false) #end>$host</option>
+					#end
+					<option value="">any host</option>
+					</select></li>
+					<li>You can enter a query in the field below. Only events coming from a host whose name matches this query will be sent to you.<br/>
+					<table style="margin:5px 0px;">
+						<tr class="odd">
+							<th>host</th>
+							<td width="25%">contains <img class="help" alt="help" src="images/help.png" longdesc="substring" title="substring"/>
+							</td>
+							<td>
+								<input type="text" name="host_query" style="width:80%;" value="$!prefill.host_query"/>
+							</td>
+						</tr>
+						</table>
+						If you enter a query, the selection made in the list above will be ignored.
+						</li>
+					</ol>
+					If you just click &quot;continue&quot;, only events coming from the <em>current host</em> will be sent to you.
+					</div>
+				</div>
+				<p class="explanation">
+				In the next steps, you will be able to choose the collection(s) to monitor, and you will be able to configure the way you will be notified of events matching your subscription.
+				</p>
+				<input type="hidden" name="current_page" value="host"/>
+				<input type="submit" name="next_page" value="back"/><input type="submit" name="next_page" value="next"/><input type="submit" name="next_page" value="finish"/>
+			</form>
+		</div>
+	</body>
+</html>
Index: gs3-extensions/gsdl-as/trunk/lib/templates/sub_notification.vm
===================================================================
--- gs3-extensions/gsdl-as/trunk/lib/templates/sub_notification.vm	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/lib/templates/sub_notification.vm	(revision 21052)
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!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>Andrea Schweer</title>
+		<link rel="stylesheet" href="/alerting/form.css" type="text/css"/>
+	</head>
+	<body>
+		<div id="content">
+			<h3>Create a subscription (4/4)</h3>
+			<table id="progress">
+			<tr>
+			<td class="completed">type/details</td><td class="completed">host</td><td class="completed">collection</td><td class="current">notification</td>
+			</tr>
+			</table>
+			<p>Configure the way you will be notified of events </p>
+			<form action="/alerting/service?action=$action" method="post">
+				<div><h4>Your subscription so far</h4>
+				<table class="subscription">
+				#set($evenodd = "odd")
+				#foreach($key in $preview.keySet())
+					#if(!($key == ""))
+						<tr class="$evenodd">
+							<th>$key</th>
+							<td>$preview.get($key)</td>
+						</tr>
+					#end
+					#if ($evenodd == "odd") #set($evenodd = "even") #else #set($evenodd = "odd") #end
+				#end
+				</table>
+				<p class="explanation">You can <a href="preview.html" target="_new">preview</a> which documents currently match your criteria (following the link will open a new window).</p>
+				</div>
+				<div>
+					<h4>How to be notified of events matching your subscription</h4>
+					<div class="explanation">
+					You will be able to see the subscription in the list of all your subscriptions after logging in to the alerting service. <br/>
+					You can assign a name to your subscription that makes it easier for you to recognise it: 
+					<input name="subscription_name" type="text" value="$!prefill.subscription_name"/>
+					</div>
+					<br/>
+					<table>
+						<tr class="odd">
+							<td><input name="way" type="checkbox" value="page" checked="checked" id="chck-page"/></td>
+							<td colspan="2">
+							<label for="chck-page">on my &quot;new since my last login&quot; page</label>
+							</td>
+						</tr>
+						<tr class="even">
+							<td><input name="way" type="checkbox" value="email" id="chck-email"/></td>
+							<td><label for="chck-email">via e-mail to</label>
+							</td>
+							<td>
+								<input type="text" name="email" value="$!prefill.email"/>
+							</td>
+						</tr>
+						<tr class="odd">
+							<td><input name="way" type="checkbox" value="rss" id="chck-rss"/></td>
+							<td colspan="2"><label for="chck-rss">with an <acronym title="Really Simple Syndication">RSS</acronym> feed</label> <img class="help" alt="help" src="images/help.png" longdesc="substring" title="substring"/>
+							</td>
+						</tr>
+						</table>
+				</div>
+				<br/>
+				
+				<input type="hidden" name="current_page" value="notification"/>
+				<input type="submit" name="next_page" value="back"/><input type="submit" name="next_page" value="next" disabled="disabled"/><input type="submit" name="next_page" value="finish"/>
+			</form>
+		</div>
+	</body>
+</html>
Index: gs3-extensions/gsdl-as/trunk/lib/templates/sub_type-details.vm
===================================================================
--- gs3-extensions/gsdl-as/trunk/lib/templates/sub_type-details.vm	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/lib/templates/sub_type-details.vm	(revision 21052)
@@ -0,0 +1,75 @@
+<?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>Create a subscription</title>
+		<link rel="stylesheet" href="/alerting/form.css" type="text/css"/>
+	</head>
+	<body>
+		<div id="content">
+			<h3>Create a subscription (1/4)</h3>
+			<table id="progress">
+			<tr>
+			<td class="current">type/details</td>
+			<td>host</td>
+			<td>collection</td>
+			<td>notification</td>
+			</tr>
+			</table>
+			<p>Choose the event type and how to identify the document(s) to watch</p>
+			<form action="/alerting/service?action=$action" method="post">
+				<div>
+					<h4>Event type</h4>
+					<input name="type" type="checkbox" value="new_document"  checked="checked" id="chck-new-document"/> <label for="chck-new-document">new document(s)</label><br/>
+					<input name="type" type="checkbox" value="document_changed"  id="chck-doc-changed"/> <label for="chck-doc-changed">document(s) changed</label><br/>
+					<input name="type" type="checkbox" value="document_deleted"  id="chck-doc-deleted"/> <label for="chck-doc-deleted">document(s) deleted</label><br/>
+				</div>
+				<div>
+					<h4>How to identify the document(s)</h4>
+					<table>
+						<tr class="even">
+							<th>title</th>
+							<td width="25%">matches <img class="help" alt="help" src="images/help.png" longdesc="normal Greenstone search" title="normal Greenstone search"/>
+							</td>
+							<td>
+								<input type="text" name="document_title" style="width:80%;" value="$!prefill.document_title"/>
+							</td>
+						</tr>
+						<tr class="odd">
+							<th>content</th>
+							<td>matches <img class="help" alt="help" src="images/help.png" longdesc="normal Greenstone search" title="normal Greenstone search"/>
+							</td>
+							<td>
+								<input type="text" name="document_content" style="width:80%;" value="$!prefill.document_content"/>
+							</td>
+						</tr>
+						<tr class="even">
+							<th>metadata</th>
+							<td>has field <img class="help" alt="help" src="images/help.png" longdesc="metadata field exists" title="metadata field exists"/>
+							</td>
+							<td>
+								<input type="text" name="metadata_has_field" value="$!prefill.metadata_has_field" style="width:80%;"/>
+							</td>
+						</tr>
+						<tr class="odd">
+							<th>metadata</th>
+							<td>has value <img class="help" alt="help" src="images/help.png" longdesc="metadata field exists and has value" title="metadata field exists and has value"/>
+							</td>
+							<td>
+								<input type="text" name="metadata_field" value="$!prefill.metatada_field" style="width:40%;"/> is
+								<input type="text" name="metadata_value" value="$!prefill.metadata_value" style="width:40%;"/>
+							</td>
+						</tr>
+					</table>
+				</div>
+				<p class="explanation">
+				In the next steps, you will be able to select the hosts and collections that you want to monitor for events. You will also be able to configure the way you will be notified of events matching your subscription.
+				</p>
+				<input type="hidden" name="current_page" value="type-details"/>
+				<input disabled="disabled" type="submit" name="next_page" value="back"/><input type="submit" name="next_page" value="next"/><input disabled="disabled" type="submit" name="next_page" value="finish"/>
+				<br/>
+			</form>
+		</div>
+	</body>
+</html>
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/AlertingService.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/AlertingService.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/AlertingService.java	(revision 21052)
@@ -0,0 +1,547 @@
+/*
+ * Created on Nov 22, 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.lang.reflect.Method;
+import java.net.URL;
+import java.sql.SQLException;
+import java.util.*;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.*;
+
+import org.apache.velocity.Template;
+import org.apache.velocity.context.Context;
+import org.apache.velocity.servlet.VelocityServlet;
+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;
+import org.greenstone.gsdlas.util.ArrayHelper;
+
+/**
+ * @author schweer
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class AlertingService extends VelocityServlet {
+    
+    /* (non-Javadoc)
+     * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
+     */
+    public void init(ServletConfig config) throws ServletException {
+        super.init(config);
+        System.out.println("reloading subs");
+        ProfileStore.getInstance().restoreFromDatabase();
+        try {
+            Set subs = ProfileStore.getInstance().getAllSubscriptionsFor("andrea");
+            System.out.println("reloaded " + subs.size() + " subs for andrea: ");
+            for (Iterator iter = subs.iterator(); iter.hasNext();) {
+                System.out.println(iter.next());
+            }
+        } catch (DatabaseException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+    public static final String[] actions = new String[] {
+            "createSubscription",
+            "deleteSubscription",
+            "editSubscription",
+            "showEvents",
+            "showFeed",
+            "listSubscriptions",
+            "login",
+            "register",
+            "logout",
+            "showLoginForm",
+            "showRegistrationForm"
+    };
+    
+    
+    protected Template handleRequest(HttpServletRequest req,
+            HttpServletResponse res, Context context) {
+        
+        String action = req.getParameter(Constants.ACTION_PARAM);
+
+        Map args = req.getParameterMap();
+        
+        if (action != null && action.equals("receiveEvent")) {
+            receiveEvent(args);
+            return null;
+        }
+        
+        args = normalise(args);
+        
+        if (action == null || !ArrayHelper.contains(actions, action)) {
+            String title = "Unknown action";
+            String message = "I don't know how to " + action;
+            String details = "The only actions I know are " + ArrayHelper.toString(actions);
+            return showError(context, message, details);
+        }
+                
+        String templateString = "";
+        
+        try {
+            Method method = AlertingService.class.getDeclaredMethod(action, new Class[] {Map.class, Context.class});
+            templateString = (String) method.invoke(this, new Object[] {args, context});
+        } catch (Exception e) {
+            String message = "An error has occured, I couldn't do what you told me to do.";
+            String details = e.getMessage() + " (" + e.getClass().getName() + "); "
+            	+ e.getCause() 
+            	+ " ; action is " + action;
+            return showError(context, message, details);
+        }
+        
+        Template template = null;
+        try {
+            template = getTemplate(templateString);
+        } catch (Exception e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+            try {
+                error((HttpServletRequest)context.get(REQUEST), 
+                        (HttpServletResponse)context.get(RESPONSE),
+                        e);
+            } catch (Exception e2) {
+                // TODO Auto-generated catch block
+                e2.printStackTrace();
+            }
+        }
+        return template;
+    }
+    
+    /**
+     * @param arguments
+     * @param context
+     * @return the Velocity template to use
+     * @throws Exception
+     */
+    public String createSubscription(Map arguments, Context context) throws Exception {
+        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
+        if (!UserManager.getInstance().isLoggedIn(session)) {
+            session.setAttribute("next_action", "createSubscription");
+            return showLoginForm(arguments, context);
+        }
+        if (arguments.containsKey("next_page") && arguments.get("next_page").equals("finish")) {
+            arguments.putAll(getPageArgsFromSession(session));
+            arguments.put("username", session.getAttribute("username"));
+            ProfileStore.getInstance().createSubscription(arguments);
+            return listSubscriptions(arguments, context);
+        } else {
+            return showSubscriptionWizardPage(arguments, context, true);
+        }
+    }
+
+    public String deleteSubscription(Map arguments, Context context) {
+        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
+        if (!UserManager.getInstance().isLoggedIn(session)) {
+            session.setAttribute("next_action", "deleteSubscription");
+            return showLoginForm(arguments, context);
+        }
+        String subscriptionID = (String) arguments.get("subscriptionID");
+        try {
+            ProfileStore.getInstance().deleteSubscription(subscriptionID);
+        } catch (DatabaseException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (SQLException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        return listSubscriptions(arguments, context);
+    }
+    
+    public String editSubscription(Map arguments, Context context) throws Exception {
+        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
+        if (!UserManager.getInstance().isLoggedIn(session)) {
+            session.setAttribute("next_action", "editSubscription");
+            return showLoginForm(arguments, context);
+        }
+        if (arguments.containsKey("next_page") && arguments.get("next_page").equals("finish")) {
+            ProfileStore.getInstance().changeSubscription(arguments, session);
+            return listSubscriptions(arguments, context);
+        } else {
+            return showSubscriptionWizardPage(arguments, context, false);
+        }
+    }
+    
+    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";
+    }
+    
+    public String showLoginForm(Map arguments, Context context) {
+        return "login.vm";
+    }
+    
+    public String showRegistrationForm(Map arguments, Context context) {
+        return "register.vm";
+    }
+
+    public String listSubscriptions(Map arguments, Context context) {
+        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
+        if (!UserManager.getInstance().isLoggedIn(session)) {
+            session.setAttribute("next_action", "listSubscriptions");
+            return showLoginForm(arguments, context);
+        }
+        String username = (String) session.getAttribute("username");
+        context.put("title", "List of Subscriptions for " + username);
+        try {
+            Collection subscriptions = ProfileStore.getInstance().getAllSubscriptionsFor(username); 
+            context.put("list", subscriptions);
+            return "list.vm";
+        } catch (DatabaseException de) {
+            context.put("message", "couldn't get list of subscriptions for " + username);
+            context.put("details", de.getMessage());
+            return "error.vm";
+        }
+    }
+    
+    public String login(Map arguments, Context context) throws Exception {
+        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
+        try {
+            UserManager.getInstance().loginUser(arguments, session);
+        } catch (UserManagementException e) {
+            context.put("error", Boolean.TRUE);
+            context.put("errormessage", e.getMessage());
+            return showLoginForm(arguments, context); 
+        }
+        if (session.getAttribute("next_action") != null) {
+            String nextAction = (String) session.getAttribute("next_action");
+            Method method = AlertingService.class.getDeclaredMethod(nextAction, new Class[] {Map.class, Context.class});
+            return (String) method.invoke(this, new Object[] {arguments, context});
+        }
+        return listSubscriptions(arguments, context);
+    }
+    
+    public String register(Map arguments, Context context) throws Exception {
+        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
+        try {
+            UserManager.getInstance().createUser(arguments, session);
+        } catch (UserManagementException e) {
+            context.put("error", Boolean.TRUE);
+            context.put("errormessage", e.getMessage());
+            return showRegistrationForm(arguments, context);
+        }
+        return login(arguments, context);
+    }
+    
+    public String logout(Map arguments, Context context) {
+        HttpSession session = ((HttpServletRequest)context.get(REQUEST)).getSession(true);
+        Enumeration atts = session.getAttributeNames();
+        while (atts.hasMoreElements()) {
+            session.removeAttribute((String) atts.nextElement());
+        }
+        session.invalidate();
+        return "general.vm";
+    }
+    
+    public void receiveEvent(Map rawEvent) {
+        Map event = new TreeMap();
+        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]);
+        }
+        System.out.println("receiving event " + event);
+        
+        GreenstoneCommunicator gsComm = null;
+        try {
+            String hostID = (String) event.get(Constants.HOST_ID_FIELD);
+            gsComm = new GreenstoneCommunicator(new URL(hostID));
+        } 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) {
+            System.err.println("Couldn't save events: " + e.getMessage());
+            e.printStackTrace();
+        }
+        System.out.println(matchedSubscriptions.size() + " matching subscriptions: " + matchedSubscriptions);
+    }
+
+    /**
+     * @param args
+     */
+    private Map normalise(Map args) {
+        Map result = new TreeMap();
+        for (Iterator iter = args.keySet().iterator(); iter.hasNext();) {
+            String key = (String) iter.next();
+            if (Predicate.isMultiValued(key) || key.equals("way")) {
+                // multi-valued attributes
+                String[] values = ((String[]) args.get(key));
+                result.put(key, Arrays.asList(values));
+            } else {
+                String firstValue = ((String[])args.get(key))[0];
+                result.put(key, firstValue);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * @param context
+     * @param message
+     * @param details
+     * @return
+     */
+    private Template showError(Context context, String message, String details) {
+        context.put("title", "Error");
+        context.put("message", message);
+        context.put("details", details);
+        try {
+            return getTemplate("error.vm");
+        } catch (Exception e) {
+            try {
+                super.error((HttpServletRequest)context.get("req"), 
+                        (HttpServletResponse)context.get("res"), 
+                        e);
+            } catch (Exception e2) {
+                e2.printStackTrace();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @param arguments
+     * @param context
+     * @param create
+     * @return
+     * @throws Exception
+     */
+    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")) {
+            return "sub_type-details.vm";
+        }
+        
+        String currentPage = (String) arguments.get("current_page");
+        String direction = (String) arguments.get("next_page");
+        
+        if (arguments.containsKey(Constants.HOST_QUERY_FIELD)) {
+            String hostQuery = (String) arguments.get(Constants.HOST_QUERY_FIELD);
+            if (hostQuery != null && hostQuery.length() != 0)
+                arguments.remove(Constants.HOST_ID_FIELD);
+        }
+        if (arguments.containsKey(Constants.COLLECTION_QUERY_FIELD)) {
+            String collQuery = (String) arguments.get(Constants.COLLECTION_QUERY_FIELD);
+            if (collQuery != null && collQuery.length() != 0)
+                arguments.remove(Constants.COLLECTION_ID_FIELD);
+        }
+        
+        // save page arguments
+        savePageArgsToSession(currentPage, arguments, session);
+        
+        String nextPage = getNextPage(currentPage, direction);
+
+        // fill prefill
+        context.put("prefill", getPageArgsFromSession(nextPage, session));
+        
+        // fill preview
+        context.put("preview", getPagePreview(nextPage, session));
+        
+        // get page-specific stuff
+        if (nextPage.equals("host")) {
+            String[] hostNames;
+            try {
+                GreenstoneCommunicator gsComm = new GreenstoneCommunicator();
+                hostNames = gsComm.getHostNames();
+            } catch (Exception e) {
+                hostNames = new String[] { "localhost" };
+            }
+            context.put("hostnames", hostNames);
+            session.setAttribute("hostnames", hostNames);
+        } else if (nextPage.equals("collection")) {
+            List hostNames = (List) arguments.get(Constants.HOST_ID_FIELD);
+            if (hostNames == null || hostNames.isEmpty()) {
+                hostNames = new Vector();
+                // no host names -> use host query
+                String[] hostsFromSession = (String[]) session.getAttribute("hostnames");
+                if (hostsFromSession == null || hostsFromSession.length == 0) {
+                    try {
+                        GreenstoneCommunicator gsComm = new GreenstoneCommunicator();
+                        hostsFromSession = gsComm.getHostNames();
+                    } catch (Exception e) {
+                        hostsFromSession = new String[] { "localhost" };
+                    }
+                }
+                String hostQuery = (String) arguments.get(Constants.HOST_QUERY_FIELD);
+                for (int i = 0; i < hostsFromSession.length; i++) {
+                    if (hostsFromSession[i] != null && hostsFromSession[i].indexOf(hostQuery) >= 0) {
+                        hostNames.add(hostsFromSession[i]);
+                    }
+                }
+            }
+            
+            Map collNames = new TreeMap();
+            for (Iterator iter = hostNames.iterator(); iter.hasNext();) {
+                String host = (String) iter.next();
+                Set collNamesForHost = new TreeSet();   
+                try {
+                    URL url = new URL("http://" + host + ":8080/soap/servlet/rpcrouter");
+                    GreenstoneCommunicator gsComm = new GreenstoneCommunicator(url);
+                    collNamesForHost.addAll(Arrays.asList(gsComm.getCollectionNames()));
+                } catch (Exception e) {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                }	
+                collNames.put(host, collNamesForHost);
+            }
+            context.put("collectionnames", collNames);
+            context.put("hostnames", hostNames);
+        }
+        
+        return "sub_" + nextPage + ".vm";
+    }
+
+    /**
+     * @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
+     * @return
+     */
+    private Map getPagePreview(String nextPage, HttpSession session) {
+        Map preview = new TreeMap();
+        if (nextPage.equals("type-details")) {
+            return preview;
+        }
+        preview.putAll(getPageArgsFromSession("type-details", session));
+        if (nextPage.equals("host")) {
+            return preview;
+        }
+        preview.putAll(getPageArgsFromSession("host", session));
+        if (nextPage.equals("collection")) {
+            return preview;
+        }
+        preview.putAll(getPageArgsFromSession("collection", session));
+        return preview;
+    }
+
+    /**
+     * @param currentPage
+     * @param direction
+     * @return
+     * @throws Exception
+     */
+    private String getNextPage(String currentPage, String direction) throws Exception {
+        String nextPage;
+        if (currentPage.equals("host") && direction.equals("back")) {
+            nextPage = "type-details";
+        } else if (currentPage.equals("type-details") || (currentPage.equals("collection") && direction.equals("back"))) {
+            nextPage = "host";
+        } else if (currentPage.equals("host") || (currentPage.equals("notification") && direction.equals("back"))) {
+            nextPage = "collection";
+        } else if (currentPage.equals("collection")) {
+            nextPage = "notification";
+        } else {
+            throw new Exception("unknown combination of currentPage=" + currentPage + " and nextPage=" + direction);
+        }
+        return nextPage;
+    }
+
+    /**
+     * @param page
+     * @param session
+     * @return 
+     */
+    private Map getPageArgsFromSession(String page, HttpSession session) {
+        Map pageArgs = (Map) session.getAttribute("page_args");
+        if (pageArgs == null || !pageArgs.containsKey(page))
+            return new TreeMap();
+        return (Map) pageArgs.get(page);
+    }
+
+    private Map getPageArgsFromSession(HttpSession session) {
+        Map result = new TreeMap();
+        Map pageArgs = (Map) session.getAttribute("page_args");
+        if (pageArgs != null) {
+            for (Iterator iter = pageArgs.values().iterator(); iter.hasNext();) {
+                Map args = (Map) iter.next();
+                result.putAll(args);
+            }
+        }
+        return result;
+    }
+    
+    /**
+     * @param page
+     * @param arguments
+     * @param session
+     */
+    private void savePageArgsToSession(String page, Map arguments, HttpSession session) {
+        Map pageArgs = (Map) session.getAttribute("page_args");
+        if (pageArgs == null) {
+            pageArgs = new TreeMap();
+        }
+        pageArgs.put(page, arguments);
+        session.setAttribute("page_args", pageArgs);
+    }
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/Constants.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/Constants.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/Constants.java	(revision 21052)
@@ -0,0 +1,31 @@
+/*
+ * Created on Dec 21, 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;
+
+/**
+ * @author schweer
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public abstract class Constants {
+    public static final String ACTION_PARAM = "action";
+    
+    public static final String HOST_QUERY_FIELD = "host_query";
+    public static final String COLLECTION_QUERY_FIELD = "collection_query";
+    public static final String HOST_ID_FIELD = "hostID";
+    public static final String COLLECTION_ID_FIELD = "collectionID";
+    public static final String HOST_COLLECTION_ID_FIELD = "hostCollectionID";
+    public static final String DOCUMENT_ID_FIELD = "documentID";
+    public static final String DOCUMENT_CONTENT_FIELD = "document_content";
+    public static final String DOCUMENT_TITLE_FIELD = "document_title";
+    public static final String TYPE_FIELD = "type";
+
+
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/EventStore.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/EventStore.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/EventStore.java	(revision 21052)
@@ -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.replaceAll("&", "&amp;");
+        encodedString = encodedString.replaceAll("<", "&lt;");
+        encodedString = encodedString.replaceAll(">", "&gt;");
+        encodedString = encodedString.replaceAll("\'", "&apos;");
+        encodedString = encodedString.replaceAll("\"", "&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: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/GreenstoneCommunicator.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/GreenstoneCommunicator.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/GreenstoneCommunicator.java	(revision 21052)
@@ -0,0 +1,215 @@
+/*
+ * Created on Nov 17, 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.net.URL;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.parsers.*;
+
+import org.apache.soap.Constants;
+import org.apache.soap.Fault;
+import org.apache.soap.rpc.*;
+import org.w3c.dom.*;
+
+import org.greenstone.gsdlas.util.ArrayHelper;
+
+/**
+ * @author schweer
+ * 
+ * TODO To change the template for this generated type comment go to Window -
+ * Preferences - Java - Code Style - Code Templates
+ */
+public class GreenstoneCommunicator {
+
+    private URL host;
+    private DocumentBuilder builder;
+
+    public GreenstoneCommunicator(URL host) throws Exception {
+        this.host = host;
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        try {
+            builder = factory.newDocumentBuilder();
+        } catch (ParserConfigurationException e) {
+            throw new Exception(e);
+        }
+
+    }
+
+    /**
+     * 
+     */
+    public GreenstoneCommunicator() {
+        
+        // TODO Auto-generated constructor stub
+    }
+
+    public String[] getHostNames() {
+        // TODO method implementation
+        return new String[] {"localhost"};
+    }
+    
+    public String[] getCollectionNames() throws Exception {
+        Document document = createSubsetDescriptionRequest("", "collectionList");
+
+        Element response = sendToGreenstone(document.getDocumentElement());
+        
+        NodeList nodeList = response.getElementsByTagName("collection");
+        String[] result = new String[nodeList.getLength()];
+        for (int i = 0; i < nodeList.getLength(); i++) {
+            Node node = nodeList.item(i);
+            Node name = node.getAttributes().getNamedItem("name");
+            result[i] = name.getNodeValue();
+        }
+        
+	System.out.println(ArrayHelper.toString(result));
+        
+        return result;
+    }
+    
+    public boolean supportsTextSearch(String collection) throws Exception {
+        Document document = createSubsetDescriptionRequest(collection, "serviceList");
+        
+        Element response = sendToGreenstone(document.getDocumentElement());
+        
+        NodeList nodeList = response.getElementsByTagName("service");
+        
+        boolean hasTextQuery = false;
+        for (int i = 0; i < nodeList.getLength(); i++) {
+            Node node = nodeList.item(i);
+            String name = node.getAttributes().getNamedItem("name").getNodeValue(); 
+            if (name.equals("TextQuery")) {
+                hasTextQuery = true;
+                break;
+            }
+        }
+        
+        if (!hasTextQuery) return false; // doesn't support text queries at all
+        
+        document = createSubsetDescriptionRequest(collection + "/TextQuery", "paramList");
+        
+        response = sendToGreenstone(document.getDocumentElement());
+        
+        nodeList = response.getElementsByTagName("param");
+        
+        for (int i = 0; i < nodeList.getLength(); i++) {
+            Node node = nodeList.item(i);
+            String name = node.getAttributes().getNamedItem("name").getNodeValue();
+            if (name == null) break;
+            String value = node.getAttributes().getNamedItem("type").getNodeValue();
+            if (value == null) break;
+            if (name.equals("query") && value.equals("string")) {
+                return true;
+            }
+        }
+        
+        return false;
+    }
+    
+    public Set fullTextSearch(String collection, String query) throws Exception {
+        Document document = builder.newDocument();
+        Element root = document.createElement("message");
+        document.appendChild(root);
+    
+        Element requestElement = document.createElement("request");
+        requestElement.setAttribute("lang", "en");
+        requestElement.setAttribute("type", "process");
+        requestElement.setAttribute("to", collection + "/TextQuery");
+        root.appendChild(requestElement);
+    
+        Element paramListElement = document.createElement("paramList");
+        requestElement.appendChild(paramListElement);
+    
+        Element paramElement = document.createElement("param");
+        paramElement.setAttribute("name", "query");
+        paramElement.setAttribute("value", query);
+        // BTS 3 what if numReturnedDocs < numMatchedDocs
+        paramListElement.appendChild(paramElement);
+        
+        
+        Set result = new TreeSet();
+        //DOM2Writer.serializeAsXML(root, new OutputStreamWriter(System.out));
+        
+        Element response = sendToGreenstone(document.getDocumentElement());
+        
+        NodeList documentNodes = response.getElementsByTagName("documentNode");
+        
+        for(int i = 0; i < documentNodes.getLength(); i++) {
+            Node node = documentNodes.item(i);
+            String docID = node.getAttributes().getNamedItem("nodeID").getNodeValue();
+            Matcher matcher = Pattern.compile("(\\d{8}:\\d+).*").matcher(docID);
+            if (matcher.matches()) {
+                docID = matcher.group(1);
+            }
+            result.add(docID);
+        }
+        
+        return result;
+    }
+    
+    private Element sendToGreenstone(Element message) throws Exception {
+        Call call = new Call();
+        // TODO get the uri from a configuration file
+        call.setTargetObjectURI("localsite");
+        call.setMethodName("process");
+        // set Encoding Style to use literal XML
+        call.setEncodingStyleURI(Constants.NS_URI_LITERAL_XML);
+        // Set Method Parameters - use literal xml
+        Parameter param = new Parameter("message", 
+                org.w3c.dom.Element.class,
+                message, 
+                Constants.NS_URI_LITERAL_XML);
+
+        Vector paramList = new Vector();
+        paramList.addElement(param);
+        call.setParams(paramList);
+
+        // Invoke the Service
+        Response response = call.invoke(host, "");
+
+        // Check for Faults
+        if (!response.generatedFault()) { // no error
+            // Extract Return value
+            Parameter result = response.getReturnValue();
+            return (Element) result.getValue();
+        } else {
+            //  Extract Fault Code and String
+            Fault f = response.getFault();
+            String faultCode = f.getFaultCode();
+            String faultString = f.getFaultString();
+            throw new Exception(faultCode + ": Fault occured: " + faultString);
+        }
+    }
+
+    /**
+     * @return
+     */
+    private Document createSubsetDescriptionRequest(String object, String subsetType) {
+        Document document = builder.newDocument();
+        Element root = document.createElement("message");
+        document.appendChild(root);
+    
+        Element requestElement = document.createElement("request");
+        requestElement.setAttribute("lang", "en");
+        requestElement.setAttribute("type", "describe");
+        requestElement.setAttribute("to", object);
+        root.appendChild(requestElement);
+    
+        Element paramListElement = document.createElement("paramList");
+        requestElement.appendChild(paramListElement);
+    
+        Element paramElement = document.createElement("param");
+        paramElement.setAttribute("name", "subset");
+        paramElement.setAttribute("value", subsetType);
+        paramListElement.appendChild(paramElement);
+        return document;
+    }
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/NotificationStore.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/NotificationStore.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/NotificationStore.java	(revision 21052)
@@ -0,0 +1,77 @@
+/*
+ * 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.util.*;
+
+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 NotificationStore {
+
+    private static NotificationStore instance;
+    private Map eventsForSubscriptionID = new TreeMap();
+    
+    private NotificationStore() {
+    }
+    
+    /**
+     * @return
+     */
+    public static NotificationStore getInstance() {
+        if (instance == null) instance = new NotificationStore();
+        return instance;
+    }
+    
+    public void add(Map event, Set subscriptions) {
+        encodeValues(event);
+        for (Iterator iter = subscriptions.iterator(); iter.hasNext();) {
+            Subscription sub = (Subscription) iter.next();
+            Integer subscriptionID = new Integer(sub.getId()); 
+            if (!eventsForSubscriptionID.containsKey(subscriptionID)) {
+                eventsForSubscriptionID.put(subscriptionID, new HashSet());
+            }
+            ((Set) eventsForSubscriptionID.get(subscriptionID)).add(event);
+        }
+    }
+
+    /**
+     * @param event
+     */
+    private void encodeValues(Map event) {
+        for (Iterator iter = event.keySet().iterator(); iter.hasNext();) {
+            String key = (String) iter.next();
+            String value = (String) event.get(key);
+            String encodedValue = value.replaceAll("&", "&amp;");
+            encodedValue = encodedValue.replaceAll("<", "&lt;");
+            encodedValue = encodedValue.replaceAll(">", "&gt;");
+            encodedValue = encodedValue.replaceAll("\'", "&apos;");
+            encodedValue = encodedValue.replaceAll("\"", "&quot;");
+            
+            event.put(key, encodedValue);
+        }
+    }
+
+    /**
+     * @param subscriptionID
+     * @return
+     */
+    public Set getEvents(Integer subscriptionID) {
+        if (eventsForSubscriptionID.containsKey(subscriptionID)) {
+            return Collections.unmodifiableSet((Set) eventsForSubscriptionID.get(subscriptionID));
+        }
+        return new TreeSet();
+    }
+
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/Notifier.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/Notifier.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/Notifier.java	(revision 21052)
@@ -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: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/ProfileStore.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/ProfileStore.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/ProfileStore.java	(revision 21052)
@@ -0,0 +1,416 @@
+/*
+ * Created on Oct 27, 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.sql.Connection;
+import java.sql.Statement;
+import java.util.*;
+
+import javax.servlet.http.HttpSession;
+
+import org.greenstone.gsdlas.database.DatabaseException;
+import org.greenstone.gsdlas.database.DatabaseManager;
+import org.greenstone.gsdlas.profiles.*;
+
+/**
+ * Storage for all Profiles. Singleton.
+ * 
+ * @author schweer
+ *  
+ */
+public class ProfileStore {
+
+    private static ProfileStore instance;
+
+    private Set noEqualsSubscriptions = new TreeSet();
+    private Map dontCareSubscriptions = new TreeMap();
+    private Map subscriptions = new TreeMap();
+    
+
+    private ProfileStore() {
+        // hide default constructor
+    }
+
+    /**
+     * @return the one existing instance of this class.
+     */
+    public static ProfileStore getInstance() {
+        if (instance == null) {
+            instance = new ProfileStore();
+        }
+        return instance;
+    }
+
+    /**
+     * @param subscription
+     *            the subscription to add to the profile store.
+     */
+    private void addSubscription(Subscription subscription) {
+        List documentIdPredicate = subscription.getPredicateList(Constants.DOCUMENT_ID_FIELD);
+        List collectionIdPredicate = subscription.getPredicateList(Constants.COLLECTION_ID_FIELD);
+        List hostIdPredicate = subscription.getPredicateList(Constants.HOST_ID_FIELD);
+        List typePredicate = subscription.getPredicateList(Constants.TYPE_FIELD);
+        
+        Integer subId = new Integer(subscription.getId());
+        
+        if (documentIdPredicate.isEmpty()) {
+            if (!dontCareSubscriptions.containsKey(Constants.DOCUMENT_ID_FIELD)) {
+                dontCareSubscriptions.put(Constants.DOCUMENT_ID_FIELD, new TreeSet());
+            }
+            Set list = (Set) dontCareSubscriptions.get(Constants.DOCUMENT_ID_FIELD);
+            list.add(subId);
+        }
+        if (hostIdPredicate.isEmpty()) {
+            if (collectionIdPredicate.isEmpty()) {
+                if (!dontCareSubscriptions.containsKey(Constants.COLLECTION_ID_FIELD)) {
+                    dontCareSubscriptions.put(Constants.COLLECTION_ID_FIELD, new TreeSet());
+                }
+                Set list = (Set) dontCareSubscriptions.get(Constants.COLLECTION_ID_FIELD);
+                list.add(subId);
+            }
+        }
+        if (typePredicate.isEmpty()) {
+            if (!dontCareSubscriptions.containsKey(Constants.TYPE_FIELD)) {
+                dontCareSubscriptions.put(Constants.TYPE_FIELD, new TreeSet());
+            }
+            Set list = (Set) dontCareSubscriptions.get(Constants.TYPE_FIELD);
+            list.add(subId);
+        }
+        
+        if (documentIdPredicate.isEmpty() && collectionIdPredicate.isEmpty() 
+                && hostIdPredicate.isEmpty() && typePredicate.isEmpty()) {
+            noEqualsSubscriptions.add(subId);
+        }
+        subscriptions.put(subId, subscription);
+    }
+
+    /**
+     * @return
+     */
+    public Set getSubscriptionsWithoutEqualsPredicates() {
+        return Collections.unmodifiableSet(noEqualsSubscriptions);
+    }
+
+    /**
+     * Computes the list of equality subscriptions satisfied by the event.
+     * @param event
+     * @return
+     */
+    private Set getPartiallyMatchedSubscriptions(Map event) {
+        Set result = new TreeSet();
+        
+        EqualsPredicate predicate;
+        
+        // all subscriptions with matching type...
+        predicate = PredicateFactory.getEqualsPredicate(Constants.TYPE_FIELD, 
+                (String)event.get(Constants.TYPE_FIELD));
+        if (predicate != null)
+            result.addAll(predicate.getSubscriptionIDs());
+        
+        // ...but only those subscriptions where the doc id matches...
+        Set docIDSubs = new TreeSet();
+        predicate = PredicateFactory.getEqualsPredicate(Constants.DOCUMENT_ID_FIELD, 
+                (String)event.get(Constants.DOCUMENT_ID_FIELD));
+        if (predicate != null)
+            docIDSubs.addAll(predicate.getSubscriptionIDs());
+        docIDSubs.addAll(getDontCareSubscriptions(Constants.DOCUMENT_ID_FIELD));
+        result.retainAll(docIDSubs);
+        
+        // ...and only those subscriptions where the host/collection id matches:
+        Set hostCollIDSubs = new TreeSet();
+        //   i.e.: host|coll is "don't care"...
+        hostCollIDSubs.addAll(getDontCareSubscriptions(Constants.COLLECTION_ID_FIELD));
+        //   ...or host matches (-> coll is "don't care" for this host)...
+        predicate = PredicateFactory.getEqualsPredicate(Constants.HOST_ID_FIELD, 
+                (String)event.get(Constants.HOST_ID_FIELD));
+        if (predicate != null)
+            hostCollIDSubs.addAll(predicate.getSubscriptionIDs());
+        //   ...or host|coll matches
+        predicate = PredicateFactory.getEqualsPredicate(Constants.COLLECTION_ID_FIELD, 
+                (String)event.get(Constants.COLLECTION_ID_FIELD));
+        if (predicate != null)
+            hostCollIDSubs.addAll(predicate.getSubscriptionIDs());
+        
+        result.retainAll(hostCollIDSubs);
+        
+        return result;
+    }
+
+    /**
+     * 
+     * @param gsComm 
+     * @return
+     * @throws UnsupportedFieldException
+     */
+    private Set findMatchedQueryPredicates(Map event, GreenstoneCommunicator gsComm) {
+        Set matchedPreds = new TreeSet();
+        
+        // iterate through all document content predicates
+        // execute the query stored in the predicate
+        // predicate is matched iff the docID occurs in the query result
+        
+        for (Iterator iter = PredicateFactory.getQueryPredicates(Constants.DOCUMENT_CONTENT_FIELD).iterator(); iter.hasNext();) {
+            Predicate predicate = (Predicate) iter.next();
+            String collection = (String) event.get(Constants.COLLECTION_ID_FIELD);
+            String query = predicate.getValue();
+            Set results;
+            try {
+                results = gsComm.fullTextSearch(collection, query);
+                if (results.contains(event.get(Constants.DOCUMENT_ID_FIELD))) {
+                    matchedPreds.add(predicate);
+                }
+            } catch (Exception e) {
+            }
+        }
+        
+        return matchedPreds;
+    }
+
+    /**
+     * Computes the set of SubstringMatchPredicates satisfied by the event.
+     * @param event
+     * 
+     * @return
+     */
+    private Set findMatchedSubstringMatchPredicates(Map event) {
+        Set result = new TreeSet();
+        
+        for (Iterator iter = PredicateFactory.getAllSubstringMatchPredicates().iterator(); iter.hasNext();) {
+            Predicate predicate = (Predicate) iter.next();
+            if (predicate.isSatisfied(event)) {
+                result.add(predicate);
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Filters the event against the profiles. It uses the equality-preferred
+     * algorithm as described in Fabret, Llirbat, Pereira and Shasha:
+     * <em>Efficient Matching for Content-based Publish/Subscribe 
+     * Systems</em>.
+     * @param event
+     * @param gsComm 
+     */
+    public Set filter(Map event, GreenstoneCommunicator gsComm) {
+        Set matchedSubscriptions = new TreeSet();
+    
+        Set partiallyMatchedSubscriptions 
+        	= getPartiallyMatchedSubscriptions(event);
+    
+        Set matchedPredicates = findMatchedSubstringMatchPredicates(event);
+        matchedPredicates.addAll(findMatchedQueryPredicates(event, gsComm));
+    
+        Map numMatchedPredicates = new TreeMap();
+        for (Iterator iter = matchedPredicates.iterator(); iter.hasNext();) {
+            Predicate pred = (Predicate) iter.next();
+            for (Iterator iterator = pred.getSubscriptionIDs().iterator(); iterator
+                    .hasNext();) {
+                Integer subId = (Integer) iterator.next();
+                Subscription sub = (Subscription) subscriptions.get(subId);
+                Integer count = (Integer) numMatchedPredicates.get(subId);
+                int newCountValue = (count == null ? 1 : count.intValue() + 1);
+                numMatchedPredicates.put(subId,
+                        new Integer(newCountValue));
+            }
+        }
+    
+        Set notUnmatchedSubscriptions = partiallyMatchedSubscriptions;
+        notUnmatchedSubscriptions.addAll(noEqualsSubscriptions);
+        for (Iterator iter = notUnmatchedSubscriptions.iterator(); iter
+                .hasNext();) {
+            Integer subId = (Integer) iter.next();
+            Subscription sub = (Subscription) subscriptions.get(subId);
+            int matchedPredicatesCount = 0;
+            if (numMatchedPredicates.get(subId) != null) {
+                matchedPredicatesCount = ((Integer) numMatchedPredicates.get(subId)).intValue();
+            }
+            if (matchedPredicatesCount == sub.getNumOfNonEqualsPredicates()) {
+                matchedSubscriptions.add(sub);
+            }
+        }
+        return matchedSubscriptions;
+    }
+
+
+    /**
+     * @param arguments
+     * @throws Exception
+     */
+    public void createSubscription(Map arguments) throws Exception {
+        Subscription sub = new Subscription(arguments);
+        addSubscription(sub);
+    }
+
+    
+    /**
+     * @param subscriptionID
+     * @throws DatabaseException
+     * @throws SQLException
+     */
+    public void deleteSubscription(String subscriptionID) throws DatabaseException, SQLException {
+        Integer subID = new Integer(subscriptionID);
+        System.out.println("deleting subscription " + subscriptionID);
+        if (!subscriptions.containsKey(subID)) {
+            return;
+        }
+        Subscription sub = (Subscription) subscriptions.get(subID);
+        for (Iterator iter = dontCareSubscriptions.values().iterator(); iter.hasNext();) {
+            Set entry = (Set) iter.next();
+            if (entry.contains(subID))
+                entry.remove(subID);
+        }
+        if (noEqualsSubscriptions.contains(subID))
+            noEqualsSubscriptions.remove(subID);
+        for (Iterator iter = sub.getPredicates().iterator(); iter.hasNext();) {
+            Predicate pred = (Predicate) iter.next();
+            pred.removeSubscription(subID);
+        }
+
+        subscriptions.remove(subID);
+        
+        
+        Connection conn = DatabaseManager.getInstance().getDatabaseConnection();
+        
+        Statement statement = conn.createStatement();
+        
+        String sqlString = "DELETE FROM subscriptions WHERE id = " + subscriptionID;
+        statement.executeUpdate(sqlString);
+        
+        sqlString = "SELECT predicate FROM subs_to_predicates " +
+        		"WHERE subscription = " + subscriptionID;
+        ResultSet predicates = statement.executeQuery(sqlString);
+        while (predicates.next()) {
+            // if there aren't any other subscriptions using this predicate, delete predicate
+            int predicateID = predicates.getInt("predicate");
+            
+        }
+        
+        
+    }
+
+    /**
+     * @param arguments
+     * @param session
+     */
+    public void changeSubscription(Map arguments, HttpSession session) {
+        String subscriptionID = (String) arguments.get("subscriptionID");
+        // BTS 14 really edit the subscription
+        
+    }
+
+    /**
+     * @param username
+     * @return
+     * @throws DatabaseException
+     */
+    public Set getAllSubscriptionsFor(String username) throws DatabaseException {
+        Set result = new TreeSet();
+        try {
+            Connection conn = DatabaseManager.getInstance().getDatabaseConnection();
+            Statement statement = conn.createStatement();
+            String sqlString = "SELECT id FROM subscriptions " +
+            		"WHERE user like '" + username + "';";
+            ResultSet idList = statement.executeQuery(sqlString);
+            while (idList.next()) {
+                int id = idList.getInt("id");
+                result.add(subscriptions.get(new Integer(id)));
+            }
+        } catch (SQLException e) {
+            e.printStackTrace();
+        }
+        
+        return result;
+    }
+
+    /**
+     * @param string
+     * @return
+     */
+    private Set getDontCareSubscriptions(String field) {
+        Set result = new TreeSet();
+        if (dontCareSubscriptions.containsKey(field)) {
+            result = (Set) dontCareSubscriptions.get(field);
+        }
+        return result;
+    }
+
+    /**
+     * 
+     */
+    public void restoreFromDatabase() {
+        try {
+            Connection conn = DatabaseManager.getInstance().getDatabaseConnection();
+            Statement statement = conn.createStatement();
+            String sqlString = "SELECT * FROM subscriptions";
+            ResultSet subs = statement.executeQuery(sqlString);
+            while (subs.next()) {
+                // construct map for all predicates
+                int id = subs.getInt("id");
+                Map map = new TreeMap();
+                map.put("username", subs.getString("user"));
+                map.put("email", subs.getString("email"));
+                map.put("subscription_name", subs.getString("name"));
+                Vector ways = new Vector();
+                if (subs.getInt("rss") != 0) {
+                    ways.add("rss");
+                }
+                if (subs.getInt("page") != 0) {
+                    ways.add("page");
+                }
+                map.put("way", ways);
+                Statement stmnt = conn.createStatement();
+                sqlString = "SELECT p.type, p.field, p.value from subs_to_predicates stp join predicates p on(stp.predicate = p.id) where stp.subscription = " + id + ";";
+                ResultSet predicates = stmnt.executeQuery(sqlString);
+                while (predicates.next()) {
+                    String type = predicates.getString(Constants.TYPE_FIELD);
+                    String field = predicates.getString("field");
+                    if (type.equals(SubstringMatchPredicate.class.getName())) {
+                        if (field.equals(Constants.COLLECTION_ID_FIELD)) {
+                            field = Constants.COLLECTION_QUERY_FIELD;
+                        } else if (field.equals(Constants.HOST_ID_FIELD)) {
+                            field = Constants.HOST_QUERY_FIELD;
+                        }
+                    }
+                    String value = predicates.getString("value");
+                    if (type.equals(EqualsPredicate.class.getName())) {
+                        if (!map.containsKey(field)) {
+                            map.put(field, new Vector());
+                        }
+                        ((List) map.get(field)).add(value);
+                    } else {
+                        map.put(field, value);
+                    }
+                }
+                try {
+                    createSubscription(map);
+                } catch (Exception e1) {
+                    // TODO Auto-generated catch block
+                    e1.printStackTrace();
+                }
+            }
+        } catch (SQLException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (DatabaseException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * @param i
+     * @return
+     */
+    public Subscription getSubscription(int id) {
+        return (Subscription) subscriptions.get(new Integer(id));
+    }
+    
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/UnsupportedFieldException.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/UnsupportedFieldException.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/UnsupportedFieldException.java	(revision 21052)
@@ -0,0 +1,21 @@
+/*
+ * Created on Nov 8, 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;
+
+/**
+ * @author schweer
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class UnsupportedFieldException extends Exception {
+    public UnsupportedFieldException(String msg) {
+        super(msg);
+    }
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/database/DatabaseException.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/database/DatabaseException.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/database/DatabaseException.java	(revision 21052)
@@ -0,0 +1,29 @@
+/*
+ * Created on Dec 1, 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.database;
+
+/**
+ * @author andrea
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class DatabaseException extends Exception {
+    
+    public DatabaseException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    /**
+     * @param string
+     */
+    public DatabaseException(String message) {
+        super(message);
+    }
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/database/DatabaseManager.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/database/DatabaseManager.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/database/DatabaseManager.java	(revision 21052)
@@ -0,0 +1,56 @@
+/*
+ * Created on Dec 1, 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.database;
+
+import java.sql.*;
+import java.sql.Connection;
+import java.sql.DriverManager;
+
+
+/**
+ * @author andrea
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class DatabaseManager {
+
+    private static DatabaseManager instance;
+    private Connection database;
+
+    private DatabaseManager() {
+        // hide constructor
+    }
+    
+    public static DatabaseManager getInstance() {
+        if (instance == null) {
+            instance = new DatabaseManager();
+        }
+        return instance;
+    }
+    
+    public Connection getDatabaseConnection() throws DatabaseException {
+        if(database == null) {
+            String connectionString = "jdbc:mysql://localhost/alerting?user=gsdl3admin";
+            try {
+                Class.forName("com.mysql.jdbc.Driver").newInstance();
+                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);
+            }
+        }
+        return database;
+    }
+    
+    
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/EqualsPredicate.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/EqualsPredicate.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/EqualsPredicate.java	(revision 21052)
@@ -0,0 +1,35 @@
+/*
+ * Created on Nov 1, 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.profiles;
+
+import java.util.Map;
+
+
+/**
+ * @author schweer
+ * 
+ * TODO To change the template for this generated type comment go to Window -
+ * Preferences - Java - Code Style - Code Templates
+ */
+public class EqualsPredicate extends Predicate {
+
+    public EqualsPredicate(String field, String id) {
+        super.field = field;
+        super.value = id;
+    }
+
+    /* (non-Javadoc)
+     * @see org.greenstone.gsdlas.profiles.Predicate#isSatisfied(java.util.Map)
+     */
+    public boolean isSatisfied(Map event) {
+        String value = (String) event.get(field);
+        return value != null && value.equals(value);
+    }
+
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/ParseException.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/ParseException.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/ParseException.java	(revision 21052)
@@ -0,0 +1,26 @@
+/*
+ * Created on Nov 1, 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.profiles;
+
+/**
+ * Exception thrown when a Predicate cannot be created due to a syntax error.
+ * @author schweer
+ * 
+ */
+public class ParseException extends Exception {
+
+    /**
+     * @param message the exception message.
+     * @see Exception#Exception(String)
+     */
+    public ParseException(String message) {
+        super(message);
+    }
+
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/Predicate.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/Predicate.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/Predicate.java	(revision 21052)
@@ -0,0 +1,175 @@
+/*
+ * Created on Nov 2, 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.profiles;
+
+import java.sql.*;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.*;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.greenstone.gsdlas.Constants;
+import org.greenstone.gsdlas.database.DatabaseException;
+import org.greenstone.gsdlas.database.DatabaseManager;
+import org.greenstone.gsdlas.util.ArrayHelper;
+
+/**
+ * @author schweer
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public abstract class Predicate implements Comparable {
+
+    protected Set subscriptions = new TreeSet();
+    protected String field;
+    protected String value;
+    protected int id;
+    
+
+    public static final String[] singleValueFields = new String[] {
+            Constants.DOCUMENT_TITLE_FIELD,
+            Constants.DOCUMENT_CONTENT_FIELD,
+            "metadata_has_field",
+            Constants.HOST_QUERY_FIELD,
+            Constants.COLLECTION_QUERY_FIELD };
+    public static final String[] multiValueFields = new String[] {
+            Constants.TYPE_FIELD,
+            Constants.HOST_ID_FIELD,
+            Constants.COLLECTION_ID_FIELD,
+            Constants.HOST_COLLECTION_ID_FIELD,
+            Constants.DOCUMENT_ID_FIELD };
+    
+    public void addSubscription(int subscriptionID) {
+        subscriptions.add(new Integer(subscriptionID));
+    }
+
+    /* (non-Javadoc)
+     * @see org.greenstone.gsdlas.profiles.Predicate#getSubscriptions()
+     */
+    public Set getSubscriptionIDs() {
+        return Collections.unmodifiableSet(subscriptions);
+    }
+
+    public int getID() {
+        return id;
+    }
+    
+    public void setID(int i) {
+        this.id = i;
+    }
+
+    /* (non-Javadoc)
+     * @see org.greenstone.gsdlas.profiles.Predicate#getKey()
+     */
+    public String getField() {
+        return field;
+    }
+
+    /* (non-Javadoc)
+     * @see org.greenstone.gsdlas.profiles.Predicate#getValue()
+     */
+    public String getValue() {
+        return value;
+    }
+
+    public String toString() {
+        return field + "=" + value;
+    }
+    
+    public boolean equals(Object other) {
+        if (other == null || !(other instanceof Predicate)) return false;
+        return id == ((Predicate)other).getID();
+    }
+    
+    public int compareTo(Object other) {
+        Predicate otherPredicate = (Predicate) other;
+        return new Integer(id).compareTo(new Integer(otherPredicate.getID()));
+    }
+    
+    public abstract boolean isSatisfied(Map event);
+
+    /**
+     * @param key
+     * @return
+     */
+    public static boolean isSingleValued(String key) {
+        return ArrayHelper.contains(singleValueFields, key);
+    }
+
+    /**
+     * @param key
+     * @return
+     */
+    public static boolean isMultiValued(String key) {
+        return ArrayHelper.contains(multiValueFields, key);
+    }
+
+    /**
+     * @return
+     * @throws DatabaseException
+     * @throws SQLException
+     */
+    int saveToDatabase() throws SQLException, DatabaseException {
+        String query = "SELECT id FROM predicates " +
+        		"WHERE field = '" + field + 
+        		"' AND value = '" + value + 
+        		"' AND type = '" + this.getClass().getName() + "';";
+        Statement statement = DatabaseManager.getInstance().getDatabaseConnection().createStatement();
+        System.out.println(query);
+        ResultSet result = statement.executeQuery(query);
+        if (result.next()) { // predicate already exists in database
+            int id = result.getInt("id"); 
+            System.out.println("predicate already exists with id " + id);
+            return id;
+        }
+        // predicate has been newly created
+        String insert = "INSERT INTO predicates (field, type, value) " +
+        "VALUES ('" + field + "', '" + this.getClass().getName() + "', '" + value + "');";
+        System.out.println(insert);
+        statement.execute(insert);
+        result = statement.executeQuery(query);
+        if (result.next()) {
+            return result.getInt("id");
+        } else {
+            throw new DatabaseException("could not save predicate");
+        }
+    }
+
+    /**
+     * @param key
+     * @return
+     */
+    public static boolean isFieldName(String key) {
+        return ArrayHelper.contains(singleValueFields, key) || ArrayHelper.contains(multiValueFields, key);
+    }
+
+    /**
+     * @param subID
+     * @throws DatabaseException
+     * @throws SQLException
+     */
+    public void removeSubscription(Integer subID) throws SQLException, DatabaseException {
+        subscriptions.remove(subID);
+
+        Statement statement = DatabaseManager.getInstance().getDatabaseConnection().createStatement();
+        String sqlString = "DELETE FROM subs_to_predicates " +
+        	"WHERE subscription = " + subID + 
+        	" AND predicate = " + id;
+        statement.executeUpdate(sqlString);
+        
+        if (subscriptions.isEmpty()) {
+            PredicateFactory.deletePredicate(this);
+            sqlString = "DELETE FROM predicates WHERE id = " + id;
+            statement.executeUpdate(sqlString);
+        }
+    }
+    
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/PredicateFactory.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/PredicateFactory.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/PredicateFactory.java	(revision 21052)
@@ -0,0 +1,245 @@
+/*
+ * Created on Nov 1, 2004
+ * 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.profiles;
+
+import java.sql.SQLException;
+import java.util.*;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.greenstone.gsdlas.Constants;
+import org.greenstone.gsdlas.database.DatabaseException;
+
+
+/**
+ * @author schweer
+ * 
+ * TODO To change the template for this generated type comment go to Window -
+ * Preferences - Java - Code Style - Code Templates
+ */
+public abstract class PredicateFactory {
+
+    private static int id = 0; 
+    
+    private static Map equalsPredicates = new TreeMap();
+    
+    // substring for collection_name -> SubstringMatchPredicates referring to it
+    private static Map collectionNameMatchPredicates = new TreeMap();
+    private static Map hostNameMatchPredicates = new TreeMap();
+    
+    // query -> QueryPredicates referring to it
+    private static Map documentTitleQueryPredicates = new TreeMap();
+    private static Map documentContentQueryPredicates = new TreeMap();
+    
+    
+    /**
+     * @param key
+     * @param value
+     * @return
+     * @throws ParseException
+     * @throws DatabaseException
+     * @throws SQLException
+     */
+    public static Predicate createPredicate(String key, String value)
+            throws SQLException, DatabaseException {
+        System.out.println("creating predicate for key " + key + " and value " + value);
+        if (!Predicate.isSingleValued(key)) {
+            return null;
+        }
+        Predicate result = null;
+        if (value == null || value.length() == 0) {
+            return null;
+        }
+        
+        if (Predicate.isMultiValued(key)) {
+            List list = new ArrayList();
+            list.add(value);
+            return (Predicate) createPredicates(key, list).get(0);
+        } else if (key.startsWith("document")) {
+            result = createQueryPredicate(key, value);
+        } else if (key.endsWith("_query")) {
+            result = createSubstringMatchPredicate(key, value);
+        } else {
+        	return null;
+        }
+        result.setID(result.saveToDatabase());
+        return result;
+    }
+    
+    public static List createPredicates(String key, List values) throws SQLException, DatabaseException {
+        if (!Predicate.isMultiValued(key)) {
+            return null;
+        }
+        List results = createEqualsPredicates(key, values);
+        for (Iterator iter = results.iterator(); iter.hasNext();) {
+            EqualsPredicate pred = (EqualsPredicate) iter.next();
+
+            pred.setID(pred.saveToDatabase());
+        }
+        return results;
+    }
+
+    /**
+     * @param key
+     * @param value
+     * @return
+     */
+    private static SubstringMatchPredicate createSubstringMatchPredicate(String key, String value) {
+        SubstringMatchPredicate predicate = null;
+        if (key.equals(Constants.COLLECTION_QUERY_FIELD)) {
+            if (collectionNameMatchPredicates.containsKey(value)) {
+                predicate = (SubstringMatchPredicate) collectionNameMatchPredicates.get(value);
+            } else {
+                predicate = new SubstringMatchPredicate(Constants.COLLECTION_ID_FIELD, value);
+                collectionNameMatchPredicates.put(value, predicate);
+            }
+        } else if (key.equals(Constants.HOST_QUERY_FIELD)) {
+            if (hostNameMatchPredicates.containsKey(value)) {
+                predicate = (SubstringMatchPredicate) hostNameMatchPredicates.get(value);
+            } else {
+                predicate = new SubstringMatchPredicate(Constants.HOST_ID_FIELD, value);
+                hostNameMatchPredicates.put(value, predicate);
+            }
+        }
+        // TODO other fields?
+        return predicate;
+    }
+
+    /**
+     * @param key
+     * @param values
+     * @return
+     */
+    private static List createEqualsPredicates(String key, List values) {
+        List result = new Vector();
+        
+        Map keyToPredicates;
+        if (!equalsPredicates.containsKey(key)) {
+            keyToPredicates = new HashMap();
+            equalsPredicates.put(key, keyToPredicates);
+        }
+        keyToPredicates = (Map) equalsPredicates.get(key);
+        
+        for (Iterator iter = values.iterator(); iter.hasNext();) {
+            String value = (String) iter.next();
+            if (value == null || value.length() == 0) {
+                continue;
+            }
+
+            if (key.equals(Constants.HOST_ID_FIELD) && !(value.startsWith("http://"))) {
+                value = "http://" + value + ":8080/soap/servlet/rpcrouter";
+            }
+            
+            EqualsPredicate predicate = null;
+            if (keyToPredicates.containsKey(value)) {
+                predicate = (EqualsPredicate) keyToPredicates.get(value);
+            } else {
+                predicate = new EqualsPredicate(key, value);
+                keyToPredicates.put(value, predicate);
+            }
+            result.add(predicate);
+        }
+        return result;
+    }
+    
+    /**
+     * @param field
+     * @param query
+     * @return
+     */
+    private static QueryPredicate createQueryPredicate(String field,
+            String query) {
+        QueryPredicate predicate = null;
+        if (field.equals(Constants.DOCUMENT_CONTENT_FIELD)) {
+            if (!documentContentQueryPredicates.containsKey(query)) {
+                predicate = new QueryPredicate(field, query);
+                documentContentQueryPredicates.put(query, predicate);
+            }
+            return (QueryPredicate) documentContentQueryPredicates.get(query);
+        } else if (field.equals(Constants.DOCUMENT_TITLE_FIELD)) {
+            if (!documentTitleQueryPredicates.containsKey(query)) {
+                predicate = new QueryPredicate(field, query);
+                documentTitleQueryPredicates.put(query, predicate);
+            }
+            return (QueryPredicate) documentTitleQueryPredicates.get(query);
+        }
+        return predicate;
+    }
+
+    public static Collection getAllQueryPredicates() {
+        Set result = new TreeSet();
+        result.addAll(documentTitleQueryPredicates.values());
+        result.addAll(documentContentQueryPredicates.values());
+        return Collections.unmodifiableSet(result);
+    }
+    
+    public static Collection getQueryPredicates(String field) {
+        Set result = new TreeSet();
+        if (field.equals(Constants.DOCUMENT_TITLE_FIELD)) {
+            result.addAll(documentTitleQueryPredicates.values());
+        } else if (field.equals(Constants.DOCUMENT_CONTENT_FIELD)) {
+            result.addAll(documentContentQueryPredicates.values());
+        }
+        return Collections.unmodifiableSet(result);
+    }
+    
+    public static Collection getAllSubstringMatchPredicates() {
+        Collection result = new Vector();
+        result.addAll(hostNameMatchPredicates.values());
+        result.addAll(collectionNameMatchPredicates.values());
+        return Collections.unmodifiableCollection(result);
+    }
+
+    /**
+     * @param string
+     * @param string2
+     * @return
+     */
+    public static EqualsPredicate getEqualsPredicate(String key, String value) {
+        EqualsPredicate result = null; 
+        if (equalsPredicates.containsKey(key)) {
+            result = (EqualsPredicate) ((Map)equalsPredicates.get(key)).get(value);
+        }
+        return result;
+    }
+
+    /**
+     * @param predicate
+     */
+    public static void deletePredicate(Predicate predicate) {
+        String field = predicate.getField();
+        String value = predicate.getValue();
+        
+        if (predicate instanceof EqualsPredicate) {
+            Map map = (Map) equalsPredicates.get(field);
+            if (map.containsKey(value)) {
+                map.remove(value);
+            }
+        } else if (predicate instanceof QueryPredicate) {
+            if (field.equals(Constants.DOCUMENT_TITLE_FIELD)) {
+                documentTitleQueryPredicates.remove(value);
+            } else if (field.equals(Constants.DOCUMENT_CONTENT_FIELD)) {
+                documentContentQueryPredicates.remove(value);
+            } else {
+                System.err.println("problem while removing: unknown field " + field);
+            }
+        } else if (predicate instanceof SubstringMatchPredicate) {
+            if (field.equals(Constants.HOST_ID_FIELD)) {
+                hostNameMatchPredicates.remove(value);
+            } else if (field.equals(Constants.COLLECTION_ID_FIELD)) {
+                collectionNameMatchPredicates.remove(value);
+            } else {
+                System.err.println("problem while removing: unknown field " + field);
+            }
+        } else {
+            System.err.println("problem while removing: unknown class " + predicate.getClass().getName());
+        }
+    }
+    
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/Profile.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/Profile.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/Profile.java	(revision 21052)
@@ -0,0 +1,33 @@
+/*
+ * Created on Oct 27, 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.profiles;
+
+import java.util.List;
+import java.util.Vector;
+
+/**
+ * @author schweer
+ * 
+ * TODO To change the template for this generated type comment go to Window -
+ * Preferences - Java - Code Style - Code Templates
+ */
+public class Profile {
+    private List subscriptions;
+
+    private String user;
+
+    public Profile(String user) {
+        this.user = user;
+        subscriptions = new Vector();
+    }
+
+    public void add(Subscription sub) {
+        subscriptions.add(sub);
+    }
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/QueryPredicate.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/QueryPredicate.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/QueryPredicate.java	(revision 21052)
@@ -0,0 +1,37 @@
+/*
+ * Created on Nov 1, 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.profiles;
+
+import java.util.*;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * @author schweer
+ * 
+ * TODO To change the template for this generated type comment go to Window -
+ * Preferences - Java - Code Style - Code Templates
+ */
+public class QueryPredicate extends Predicate {
+    
+    private Set containsWords = new TreeSet();
+    private Set notContainsWords = new TreeSet();
+    
+    public QueryPredicate (String field, String query) {
+        super.field = field;
+        super.value = query;
+    }
+    public boolean isSatisfied(Map event) {
+        return true;
+    }
+    
+    public String toString() {
+        return field + " matches " + value;
+    }
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/Subscription.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/Subscription.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/Subscription.java	(revision 21052)
@@ -0,0 +1,300 @@
+/*
+ * Created on Oct 27, 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.profiles;
+
+import java.sql.*;
+import java.sql.Connection;
+import java.sql.Statement;
+import java.util.*;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.greenstone.gsdlas.Constants;
+import org.greenstone.gsdlas.database.DatabaseException;
+import org.greenstone.gsdlas.database.DatabaseManager;
+
+/**
+ * @author schweer
+ * 
+ * TODO To change the template for this generated type comment go to Window -
+ * Preferences - Java - Code Style - Code Templates
+ */
+public class Subscription implements Comparable {
+    private Map map;
+    
+    private int id;
+    private String username;
+    private String name;
+    private String email;
+    private boolean rssNotification;
+    private boolean eventsSincePageNotification;
+    
+    private int numOfNonEqualsPredicates;
+    
+    public Subscription(Map valueMap) throws DatabaseException, SQLException {
+        valueMap = cleanHostCollectionIDs(valueMap);
+        map = new TreeMap();
+        for (Iterator iter = valueMap.keySet().iterator(); iter.hasNext();) {
+            String key = (String) iter.next();
+            
+            if (!Predicate.isFieldName(key)) {
+                continue;
+            }
+            
+            Object value = valueMap.get(key);
+            
+            if (value instanceof String) {
+                Predicate predicate = PredicateFactory.createPredicate(key, (String) value);  
+                map.put(key, predicate);
+
+                if (predicate != null && !(predicate instanceof EqualsPredicate)) {
+                    numOfNonEqualsPredicates++;
+                }
+            } else if (value instanceof List) {
+                List values = (List)value;
+                List predicates = PredicateFactory.createPredicates(key, values);
+                map.put(key, predicates);
+                for (Iterator iterator = predicates.iterator(); iterator
+                        .hasNext();) {
+                    Predicate predicate = (Predicate) iterator.next();
+                    
+                    if (predicate != null && !(predicate instanceof EqualsPredicate)) {
+                        numOfNonEqualsPredicates++;
+                    }
+                }
+            } 
+            
+        }
+        
+        System.out.println("finished creating predicates");
+        
+        username = (String) valueMap.get("username");
+        name = (String) valueMap.get("subscription_name");
+        email = (String) valueMap.get("email");
+        rssNotification = valueMap.containsKey("way") && ((List)valueMap.get("way")).contains("rss");
+        eventsSincePageNotification = valueMap.containsKey("way") && ((List)valueMap.get("way")).contains("page");
+        id = saveToDatabase();
+        
+        for (Iterator iter = getPredicates().iterator(); iter.hasNext();) {
+            Predicate predicate = (Predicate) iter.next();
+            if (predicate != null) {
+                predicate.addSubscription(id);
+            }
+        }
+    }
+    
+    /**
+     * @param valueMap
+     * @return
+     */
+    private Map cleanHostCollectionIDs(Map valueMap) {
+        Map result = new TreeMap();
+        Pattern pattern = Pattern.compile("^(.*)\\|(.*)$");
+        for (Iterator iter = valueMap.keySet().iterator(); iter.hasNext();) {
+            String key = (String) iter.next();
+            if (!key.equals(Constants.HOST_COLLECTION_ID_FIELD)) {
+                result.put(key, valueMap.get(key));
+                continue;
+            }
+            if (key.equals(Constants.HOST_ID_FIELD)) { continue; }
+            
+            List values = (List) valueMap.get(key);
+            List hostIDs = new Vector();
+            List collIDs = new Vector();
+            for (Iterator iterator = values.iterator(); iterator.hasNext();) {
+                String id = (String) iterator.next();
+                Matcher matcher = pattern.matcher(id);
+                if (matcher.matches()) {
+                    String host = null;
+                    String coll = null;
+                    try {
+                        host = matcher.group(1);
+                        coll = matcher.group(2);
+                    } catch (IndexOutOfBoundsException e) { /*ignore*/ }
+                    if (host != null && host.length() > 0) {
+                        if (coll != null && coll.length() > 0) {
+                            if (!collIDs.contains(id)) collIDs.add(id);
+                        } else {
+                            if (!hostIDs.contains(host)) hostIDs.add(host);
+                        }
+                    }
+                }
+            }
+            result.put(Constants.HOST_ID_FIELD, hostIDs);
+            result.put(Constants.COLLECTION_ID_FIELD, collIDs);
+        }
+        return result;
+    }
+
+    public boolean containsKey(Object key) {
+        return map.containsKey(key);
+    }
+    
+    public boolean containsValue(Object value) {
+        return map.containsValue(value);
+    }
+    
+    public Predicate getPredicate(String field) {
+        if (map.get(field) instanceof Predicate) {
+            return (Predicate) map.get(field);
+        }
+        return null;
+    }
+    
+    public List getPredicateList(String field) {
+        if (map.get(field) instanceof List) {
+            return (List) map.get(field);
+        }
+        return new Vector();
+    }
+    
+    public Set keySet() {
+        return map.keySet();
+    }
+    
+    public Set getPredicates() {
+        Set result = new HashSet();
+        for (Iterator iter = map.values().iterator(); iter.hasNext();) {
+            Object next = iter.next();
+            if (next == null) {
+                continue;
+            } else if (next instanceof Predicate) {
+                result.add(next);
+            } else if (next instanceof List) {
+                result.addAll((Collection) next);
+            }
+        }
+        return result;
+    }
+    
+    /**
+     * @return
+     */
+    public int getId() {
+        return id;
+    }
+    
+    public int getNumOfNonEqualsPredicates() {
+        return numOfNonEqualsPredicates;
+    }
+    
+    public String toString() {
+        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();
+    }
+    
+    public boolean equals(Object other) {
+        if (other == null || !(other instanceof Subscription)) return false;
+        return id == ((Subscription)other).id;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Comparable#compareTo(java.lang.Object)
+     */
+    public int compareTo(Object arg0) {
+        Subscription other = (Subscription) arg0;
+        return new Integer(id).compareTo(new Integer(other.id));
+    }
+    
+    private int saveToDatabase() throws DatabaseException, SQLException {
+        Connection conn = DatabaseManager.getInstance().getDatabaseConnection();
+        Statement statement = conn.createStatement();
+        String sqlString;
+        boolean initial = true;
+        
+        sqlString = "SELECT id FROM subscriptions WHERE name like '" + name + 
+        			"' AND email like '" + email + "' AND rss=" + (rssNotification ? 1 : 0) 
+        			+ " AND page=" + (eventsSincePageNotification ? 1 : 0) + " AND user like '" +
+        			username + "';";
+        ResultSet result = statement.executeQuery(sqlString);
+        if (result.next()) {
+            initial = false;
+            this.id = result.getInt("id");
+        }
+        
+        if (initial) {
+            sqlString = "INSERT INTO subscriptions (name, email, rss, page, user) " +
+            		"VALUES ('" + name + "','" + email + "'," + (rssNotification ? 1 : 0) +
+            		"," + (eventsSincePageNotification ? 1: 0)+ ", '" + username + "');";
+        } else {
+            sqlString = "UPDATE subscriptions SET name='" + name + "', email='" +
+            		email + "', rss=" + (rssNotification ? 1 : 0) + ",page=" + 
+            		(eventsSincePageNotification ? 1 : 0)+ " WHERE id=" + this.id + ";";
+            // cannot change user
+        }
+        statement.executeUpdate(sqlString);
+        
+        sqlString = "SELECT id FROM subscriptions WHERE name like '" + name + 
+        			"' AND email like '" + email + "' AND rss=" + (rssNotification ? 1 : 0) 
+        			+ " AND page=" + (eventsSincePageNotification ? 1 : 0) + " AND user like '" +
+        			username + "';";
+        System.out.println(sqlString);
+        result = statement.executeQuery(sqlString);
+        
+        if (result.next()) {
+            id = result.getInt("id");
+        } else {
+            throw new DatabaseException("Couldn't save subscription");
+        }
+        
+        if (initial) {
+           for (Iterator iter = getPredicates().iterator(); iter.hasNext();) {
+               Predicate predicate = (Predicate) iter.next();
+               sqlString = "INSERT INTO subs_to_predicates (subscription, predicate) " +
+               		"VALUES (" + id + "," + predicate.getID() + ");";
+               statement.executeUpdate(sqlString);
+           }
+        }
+        return id;
+    }
+
+    /**
+     * @return
+     */
+    public boolean wantsEMailNotification() {
+        return email != null && email.length() > 1;
+    }
+
+    /**
+     * @return
+     */
+    public String getMailAddress() {
+        return email;
+    }
+    
+    public String getName() {
+        return name;
+    }
+    
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/SubstringMatchPredicate.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/SubstringMatchPredicate.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/profiles/SubstringMatchPredicate.java	(revision 21052)
@@ -0,0 +1,39 @@
+/*
+ * Created on Nov 1, 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.profiles;
+
+import java.util.Map;
+
+
+/**
+ * @author schweer
+ * 
+ * TODO To change the template for this generated type comment go to Window -
+ * Preferences - Java - Code Style - Code Templates
+ */
+public class SubstringMatchPredicate extends Predicate {
+
+    public SubstringMatchPredicate(String field, String substring) {
+        super.field = field;
+        super.value = substring;
+    }
+
+    /* (non-Javadoc)
+     * @see org.greenstone.gsdlas.profiles.Predicate#isSatisfied(java.util.Map)
+     */
+    public boolean isSatisfied(Map event) {
+        String fieldValue = (String) event.get(field);
+        return fieldValue != null && fieldValue.indexOf(value) >= 0;
+    }
+
+    public String toString() {
+        return field + " contains " + value;
+    }
+    
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/users/PasswordMismatchException.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/users/PasswordMismatchException.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/users/PasswordMismatchException.java	(revision 21052)
@@ -0,0 +1,21 @@
+/*
+ * Created on Dec 3, 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.users;
+
+/**
+ * @author andrea
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class PasswordMismatchException extends Exception {
+    public PasswordMismatchException(String message) {
+        super(message);
+    }
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/users/UserManagementException.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/users/UserManagementException.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/users/UserManagementException.java	(revision 21052)
@@ -0,0 +1,31 @@
+/*
+ * Created on Dec 3, 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.users;
+
+
+/**
+ * @author andrea
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class UserManagementException extends Exception {
+
+    /**
+     * @param e
+     */
+    public UserManagementException(String message, Throwable cause) {
+        super(message, cause);
+    }
+    
+    public UserManagementException(String message) {
+        super(message);
+    }
+
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/users/UserManager.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/users/UserManager.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/users/UserManager.java	(revision 21052)
@@ -0,0 +1,121 @@
+/*
+ * Created on Dec 1, 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.users;
+
+import java.sql.*;
+import java.util.Map;
+
+import javax.servlet.http.HttpSession;
+
+import org.greenstone.gsdlas.database.DatabaseManager;
+
+/**
+ * @author andrea
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class UserManager {
+    private static UserManager instance;
+    
+    private UserManager() {
+        // hide constructor
+    }
+    
+    static public UserManager getInstance() {
+        if (instance == null) {
+            instance = new UserManager();
+        }
+        return instance;
+    }
+
+    /**
+     * @param session
+     * @return
+     */
+    public boolean isLoggedIn(HttpSession session) {
+        if (session.getCreationTime() - session.getLastAccessedTime() > session.getMaxInactiveInterval())
+            return false;
+        return session.getAttribute("username") != null;
+    }
+
+    /**
+     * @param arguments
+     * @param session
+     * @throws PasswordMismatchException
+     * @throws UserManagementException
+     */
+    public void createUser(Map arguments, HttpSession session) throws PasswordMismatchException, UserManagementException {
+        if (!arguments.get("password").equals(arguments.get("password2"))) {
+            throw new PasswordMismatchException("The passwords don't match");
+        }
+        String username = (String) arguments.get("username");
+        
+        byte[] password = ((String) arguments.get("password")).getBytes();
+        
+        byte[] pwdHash = password;
+//        try {
+//            pwdHash = MessageDigest.getInstance("MD5").digest(password);
+//        } catch (NoSuchAlgorithmException e) {
+//            e.printStackTrace();
+//            throw new UserManagementException("could not create user", e);
+//        }
+        
+        try {
+            Connection conn = DatabaseManager.getInstance().getDatabaseConnection();
+            Statement statement = conn.createStatement();
+            statement.executeUpdate("INSERT INTO users (username, password) " +
+            		"VALUES ('" + username + "','" + new String(pwdHash) + "')");
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new UserManagementException("could not create user", e);
+        }
+        
+    }
+
+    /**
+     * @param arguments
+     * @param session
+     * @throws UserManagementException
+     */
+    public void loginUser(Map arguments, HttpSession session) throws UserManagementException {
+        String username = (String) arguments.get("username");
+        
+        if (isLoggedIn(session) && session.getAttribute("username").equals(username)) {
+            return; // already logged in
+        }
+        
+        byte[] password = ((String) arguments.get("password")).getBytes();
+        
+        byte[] pwdHash = password;
+        try {
+//            pwdHash = MessageDigest.getInstance("MD5").digest(password);
+            
+            Connection conn = DatabaseManager.getInstance().getDatabaseConnection();
+            Statement statement = conn.createStatement();
+            ResultSet results = statement.executeQuery("SELECT password " +
+            		"FROM users WHERE username like '" + username + "';");
+            String pwdFromDB = "";
+            if(results.next()) {
+                pwdFromDB = results.getString("password");
+            }
+            if (!pwdFromDB.equals(new String(pwdHash))) {
+                throw new PasswordMismatchException("user " + username 
+                        + " is unknown, or the passwords don't match");
+            }
+            session.setAttribute("username", username);
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new UserManagementException("could not login user", e);
+        }
+
+    }
+    
+    
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/util/ArrayHelper.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/util/ArrayHelper.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/util/ArrayHelper.java	(revision 21052)
@@ -0,0 +1,40 @@
+/*
+ * Created on Dec 5, 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.util;
+
+import java.util.Arrays;
+
+/**
+ * @author andrea
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class ArrayHelper {
+    static public boolean contains(String[] array, String element) {
+        if (array == null || element == null) {
+            return false;
+        }
+        Arrays.sort(array);
+        int index = Arrays.binarySearch(array, element);
+        return index > -1 && index <= array.length;
+    }
+
+    static public String toString(String [] array) {
+	StringBuffer buffer = new StringBuffer();
+	buffer.append("[");
+	for (int i=0; i<array.length; i++) {
+	    buffer.append((String)array[i]);
+	    buffer.append(",");
+	}
+	buffer.append("]");
+	return buffer.toString();
+
+    }
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/util/Mailer.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/util/Mailer.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/util/Mailer.java	(revision 21052)
@@ -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: gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/util/PatriciaTree.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/util/PatriciaTree.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/gsdlas/util/PatriciaTree.java	(revision 21052)
@@ -0,0 +1,242 @@
+/*
+ * Created on Nov 11, 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.util;
+
+import java.util.*;
+
+/**
+ * @author andrea
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class PatriciaTree {
+	
+	private Node root;
+	
+	public PatriciaTree () {
+	}
+	
+	public void put(CharSequence key, Object value) {
+		root = put(key, value, root, 0);
+	}
+	
+	public Set get(CharSequence key) {
+		return get(key, root, 0);
+	}
+	
+	public String toString() {
+	    return toPreorderString(root).toString();
+	}
+	
+	private StringBuffer toPreorderString(Node root) {
+	    StringBuffer buffer = new StringBuffer("(\n");
+	    buffer.append(root);
+	    buffer.append("\nchildren:");
+	    if (root.hasChildren()) {
+	        for (Iterator iter = root.children.keySet().iterator(); iter.hasNext();) {
+	            Character key = (Character) iter.next();
+	            Node child = root.getChild(key);
+	            buffer.append("\n");
+	            buffer.append(key);
+	            buffer.append("->");
+	            buffer.append(toPreorderString(child));
+	        }
+	    }
+	    
+	    buffer.append("\n)");
+	    return buffer;
+	}
+	
+	/**
+	 * @param key
+	 * @param root
+	 * @param position
+	 * @return the set of values associated with <code>key</code>
+	 */
+	private Set get(CharSequence key, Node root, int position) {
+		Set result = new HashSet();
+		
+		int i = findFirstMismatch(key, root, position);
+		
+		if (i == root.skipped.length()) {
+			Character nextChar = new Character(key.charAt(position + i));
+			if (key.length() > position + i + 1) { // nextChar is not last character
+				if (root.hasChild(nextChar)) {
+					result.addAll(get(key, root.getChild(nextChar), position + i + 1));
+				}	
+			} else {
+				result.addAll(root.getValues(nextChar));
+			}
+		}
+		
+		return result;
+	}
+
+	/**
+	 * @param key
+	 * @param value
+	 * @param root
+	 * @param position
+	 * @return the new root node
+	 */
+	private Node put(CharSequence key, Object value, Node root, int position) {
+	    if (root == null) {
+	        root = new Node();
+	        root.skipped = key.subSequence(position, key.length() - 1);
+	        root.addValue(new Character(key.charAt(key.length() - 1)), value);
+	        return root;
+	    }
+	    
+	    if (root.skipped == null || root.skipped.length() == 0) {
+	        addAsValueOrChild(key, value, root, position);
+	        return root;
+	    }
+	    
+	    int i = findFirstMismatch(key, root, position);
+	    if (i >= root.skipped.length() && position + i < key.length()) {
+	        addAsValueOrChild(key, value, root, position + i);
+	        return root;
+	    }
+	    
+	    if (i >= root.skipped.length() && isLastCharacter(key, position + i - 1)) {
+	        Node newRoot = new Node();
+	        newRoot.skipped = root.skipped.subSequence(0, i - 1);
+	        newRoot.addChild(new Character(root.skipped.charAt(i - 1)), root);
+	        try {
+		        root.skipped = root.skipped.subSequence(i, root.skipped.length());
+		    } catch (StringIndexOutOfBoundsException e) {
+		        root.skipped = "";
+		    }
+		    addAsValueOrChild(key, value, newRoot, position + i - 1);
+		    return newRoot;
+	    }
+	    
+	    Node newRoot = new Node();
+	    newRoot.skipped = root.skipped.subSequence(0, i);
+	    newRoot.addChild(new Character(root.skipped.charAt(i)), root);
+	    try {
+	        root.skipped = root.skipped.subSequence(i + 1, root.skipped.length());
+	    } catch (StringIndexOutOfBoundsException e) {
+	        root.skipped = "";
+	    }
+	    
+	    addAsValueOrChild(key, value, newRoot, position + i);
+	    
+	    return newRoot;
+	}
+
+    /**
+     * @param key
+     * @param value
+     * @param root
+     * @param position
+     */
+    private void addAsValueOrChild(CharSequence key, Object value, Node root, int position) {
+        Character nextChar = new Character(key.charAt(position)); 
+        if (isLastCharacter(key, position)) {
+            root.addValue(nextChar, value); 
+        } else {
+            Node newChild = put(key, value, root.getChild(nextChar), position + 1);
+            root.addChild(nextChar, newChild);
+        }
+    }
+
+    /**
+     * @param key
+     * @param position
+     * @return
+     */
+    private boolean isLastCharacter(CharSequence key, int position) {
+        return key.length() - position == 1;
+    }
+
+    /**
+	 * @param key
+	 * @param root
+	 * @param position
+	 * @return
+	 */
+	private int findFirstMismatch(CharSequence key, Node root, int position) {
+		int i = 0;
+		while (i < root.skipped.length()) {
+			try {
+				if (root.skipped.charAt(i) == key.charAt(position + i)) {
+					i++;
+				} else {
+					return i;
+				}
+			} catch (StringIndexOutOfBoundsException e) {
+				return i;
+			}
+		}
+		return i;
+	}
+
+
+	private class Node {
+		private CharSequence skipped = "";
+		private Map children;
+		private Map values;
+		
+		/**
+		 * @return
+		 */
+		public Set getValues(Character key) {
+			if (values == null || !values.containsKey(key))
+				return new HashSet();
+			return (Set) values.get(key);
+		}
+
+		boolean hasChild(Character key) {
+			return children != null && children.containsKey(key);
+		}
+		
+		boolean hasChildren() {
+			return children != null && !children.isEmpty();
+		}
+		
+		void addChild(Character key, Node child) {
+			if (children == null ) {
+				children = new TreeMap();
+			}
+			children.put(key, child);
+		}
+		
+		Node getChild(Character key) {
+			if (children == null) return null;
+			return (Node) children.get(key);
+		}
+		
+		void addValue(Character key, Object value) {
+			if (values == null) {
+				values = new TreeMap();
+			}
+			Set valueSet;
+			if (values.containsKey(key)) {
+				valueSet = (Set) values.get(key);
+			} else {
+				valueSet = new HashSet();
+				values.put(key, valueSet);
+			}
+			valueSet.add(value);
+		}
+		
+		public String toString() {
+		    StringBuffer buffer = new StringBuffer();
+		    buffer.append(skipped);
+		    buffer.append("\nvalues:");
+		    if (values != null) {
+		        buffer.append(values);
+		    }
+		    return buffer.toString();
+		}
+	}
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/AbstractClientResponse.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/AbstractClientResponse.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/AbstractClientResponse.java	(revision 21052)
@@ -0,0 +1,68 @@
+package org.greenstone.p2p;
+
+import java.util.*;
+
+public class AbstractClientResponse implements ClientResponse
+{ private boolean success;
+  private Map     values;
+
+  public AbstractClientResponse()
+  { this.success = true;
+    this.values  = new HashMap();
+  }
+  
+  public boolean isSuccessful()
+  { return this.success;
+  }
+  
+  public void setSuccessful(boolean success)
+  { this.success = success;
+  }
+  
+  public void addValue(String label, String value)
+  { if (this.values.get(label) == null)
+    { this.setValue(label, value);
+    }
+    else
+    { Vector set = (Vector) this.values.get(label);
+      set.addElement(value);
+    }
+  }
+  
+  public void setValue(String label, String value)
+  { Vector set = new Vector();
+    set.addElement(value);
+    this.values.put(label, set);
+  }
+  
+  public Vector getValues(String label)
+  { return (Vector) this.values.get(label);
+  }
+
+  public Set getLabels()
+  { return this.values.keySet();
+  }
+
+  public String toString()
+  { Iterator keys = this.values.keySet().iterator();
+
+    StringBuffer buffer = new StringBuffer();
+
+    while (keys.hasNext())
+    { String key = keys.next().toString();
+
+      buffer.append("  " +  key + ":\n");
+
+      Vector vals = (Vector) this.values.get(key);
+      Enumeration valist = vals.elements();
+      while (valist.hasMoreElements())
+	{ String nextVal = valist.nextElement().toString();
+
+	  buffer.append("    ");
+	  buffer.append(nextVal);
+	  buffer.append("\n");
+	}
+    }
+    return buffer.toString();
+  }
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/AbstractServerAction.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/AbstractServerAction.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/AbstractServerAction.java	(revision 21052)
@@ -0,0 +1,71 @@
+package org.greenstone.p2p;
+
+import java.net.*;
+import java.util.*;
+
+public abstract class AbstractServerAction implements NameServerAction
+{ NameServer host;
+
+  public AbstractServerAction(NameServer host)
+  { this.host = host;
+  }
+
+  public abstract String getActionName();
+  
+  public void propagateAction(URL forwarder, Map parameters)
+  { Enumeration subServers = host.subServers();
+  
+    // poll all child servers
+    while (subServers.hasMoreElements())
+    { URL server = (URL) subServers.nextElement();
+      
+      if (!server.equals(forwarder))
+      { // send server the action
+        String response = this.propagateActionViaServer(server, parameters, forwarder);
+      }
+    }
+    
+    // poll upwards to parent
+    if (host.getParent() != null &&
+	!host.getParent().equals(forwarder))
+    { this.propagateActionViaServer(host.getParent(), parameters, forwarder);
+    }
+  }
+
+  public void doAction(URL forwarder, Map parameters)
+  { // start propagating the action outwards
+    this.propagateAction(forwarder, parameters);
+  
+    // now execute it on our children
+    Enumeration servers = host.servers();
+  
+    while (servers.hasMoreElements())
+    { URL server = (URL) servers.nextElement();
+      
+      if (!server.equals(forwarder))
+      { // send server the action
+        String response = this.doActionOnServer(server, parameters, forwarder);
+      }
+    }
+  }
+  
+  String propagateActionViaServer(URL server, Map paramters, URL responseTo)
+  { String actionName = this.getActionName();
+  
+    // send action to server, in a separate thread
+    // get responses
+    // forward those back
+    return "";
+  }
+  
+  String doActionOnServer(URL server, Map parameters, URL responseTo)
+  { String actionName = this.getActionName();
+  
+    // send action to the destination server (as a URL)
+    
+    // get response (as HTML)
+    
+    // forward it back...
+    return "";
+  }
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/Client.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/Client.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/Client.java	(revision 21052)
@@ -0,0 +1,40 @@
+import java.io.*;
+import java.net.*;
+
+public class Client {
+    public static String read() throws IOException {
+
+        Socket echoSocket = null;
+        PrintWriter out = null;
+        BufferedReader in = null;
+
+        try {
+            echoSocket = new Socket("taranis", 7);
+            out = new PrintWriter(echoSocket.getOutputStream(), true);
+            in = new BufferedReader(new InputStreamReader(
+                                        echoSocket.getInputStream()));
+        } catch (UnknownHostException e) {
+            System.err.println("Don't know about host: taranis.");
+            System.exit(1);
+        } catch (IOException e) {
+            System.err.println("Couldn't get I/O for "
+                               + "the connection to: taranis.");
+            System.exit(1);
+        }
+
+        BufferedReader stdIn = new BufferedReader(
+                                   new InputStreamReader(System.in));
+        String userInput;
+
+        while ((userInput = stdIn.readLine()) != null) {
+            out.println(userInput);
+            System.out.println("echo: " + in.readLine());
+        }
+
+        out.close();
+        in.close();
+        stdIn.close();
+        echoSocket.close();
+	return "";
+    }
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/ClientResponse.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/ClientResponse.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/ClientResponse.java	(revision 21052)
@@ -0,0 +1,11 @@
+package org.greenstone.p2p;
+
+import java.util.*;
+
+public interface ClientResponse
+{ boolean isSuccessful(); // obvious
+  Vector  getValues(String label); // get the values associated with a given
+                                   // label; which values are available varies
+                                   // from action to action
+  Set     getLabels();             // get the labels in this response
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/DefaultServerAction.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/DefaultServerAction.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/DefaultServerAction.java	(revision 21052)
@@ -0,0 +1,13 @@
+package org.greenstone.p2p;
+
+public class DefaultServerAction extends AbstractServerAction
+{ public static final String DEFAULT_ACTION = "default";
+
+  public DefaultServerAction(NameServer host)
+  { super(host);
+  }
+
+  public String getActionName()
+  { return DEFAULT_ACTION;
+  }
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/GreenstoneClient.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/GreenstoneClient.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/GreenstoneClient.java	(revision 21052)
@@ -0,0 +1,31 @@
+package org.greenstone.p2p;
+
+import java.util.*;
+import java.net.URL;
+
+public interface GreenstoneClient
+{ /**
+   * This interface needs to be fulfilled by a Greenstone machine wanting to
+   * register and receive messages from the Greenstone Distributed Service.
+   *
+   */
+
+  /**
+   * This method is called by a GDS server when an action (message) has a response
+   * sent in reply.  The Greenstone server that sends the action will have this
+   * method called for each response received from the GDS network.
+   *
+   * @param: <code>NameServerAction</code> - at present, </code>null</code>.
+   * This method *must* be synchronized
+   * @param: <code>ClientResponse</code> - is a simple wrapper for the Map class.
+   * 
+   */
+  public void receiveResponse(NameServerAction action, ClientResponse response);
+
+  /**
+   *
+   */
+  public void receiveMessage(String message, Map parameters);
+  public String getIdentifier();
+  public URL    getURL();
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/GreenstoneInterface.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/GreenstoneInterface.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/GreenstoneInterface.java	(revision 21052)
@@ -0,0 +1,94 @@
+
+package org.greenstone.p2p;
+
+import java.net.URL;
+import java.util.*;
+
+/**
+ * This is the class that provides a set of functions for a Greenstone server to call
+ * in order to send information/messages into a GDS network.
+ */
+
+public class GreenstoneInterface
+{ private GreenstoneClient client;
+  private URL              server;
+
+  // temporary variables which will eventually disapper - ignore!
+  private static GreenstoneClient lastClient;
+  private static NameServer       nameServer = null;
+  // end of temporary variables
+
+  /**
+   * Make a new interface with a client that will receive the responses
+   * to any messages sent.
+   *
+   * @param: the client to receive responses to messages sent through this
+   *         interface.
+   */
+  public GreenstoneInterface(GreenstoneClient client)
+  { this.client = client;
+    lastClient = this.client;
+    //    /*this.*/nameServer = null;
+  }
+
+  /**
+   *  Set up a registration for this machine with a GDS server.
+   *
+   *  The Greenstone machine should take care to store the URL of the
+   *  server for later use - don't continually reregister the same
+   *  server!
+   *
+   *  @param: <code>URL</code> the location of the GDS server to register with
+   *
+   *  @return: <code>boolean</code> whether the registration was successful or not.
+   */
+  public boolean registerWithServer(URL server)
+  { // connect to server
+    // 
+    if (/*this.*/nameServer == null) {
+      /*this.*/nameServer = new NameServer(server.toString());
+    }
+    this.server = server;
+    /*this.*/nameServer.registerServer(this.client.getURL());
+
+    return true;
+  }
+
+  /**
+   *  Method to unregister from the current GDS server for this Greenstone
+   *  machine.
+   *
+   *  @return: <code>boolean</code> whether the registration was successful or not.
+   */
+  public boolean unregisterFromServer()
+  { /*this.*/nameServer = null;
+    return true;
+  }
+
+  /**
+   *  Send a message into the GDS system through the current GDS server.
+   *
+   *  Success does not guarantee that you will receive a response.
+   *
+   *  @param: <code>String</code> the identifier for this type of message
+   *  @param: <code>Map</code> a <code>Vector</code> item is associated with
+   *          each key in this map.
+   *
+   *  @return: <code>boolean</code> whether the sending was successful or not.
+   */
+  public boolean sendMessage(String message, Map parameters)
+  { if (/*this.*/nameServer != null)
+      { /*this.*/nameServer.sendMessage(this.client.getURL(), message, parameters);
+    }
+    return false;
+  }
+
+  /**
+   *  Receive a response to a message sent from this Greenstone machine and
+   *  forward it to the current client - won't be needed in the long run, it
+   *  is here at present to provide a route for returning responses.
+   */
+  static public void receiveResponse(URL greenstoneURL, ClientResponse response)
+  { lastClient.receiveResponse(null, response);
+  }
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/GreenstoneServer.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/GreenstoneServer.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/GreenstoneServer.java	(revision 21052)
@@ -0,0 +1,20 @@
+package org.greenstone.p2p;
+
+import java.net.URL;
+
+/**
+ *  Simple class to hold the location of a Greenstone server.
+ *
+ *  This class should be subclassed and methods overridden for 
+ *  Greenstone 2, 3 or any special installations such as the
+ *  Music DL.
+ */
+
+public class GreenstoneServer
+{ private URL server;
+  
+  public GreenstoneServer(URL url)
+  { this.server = url;
+  }
+
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/GreenstoneTest.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/GreenstoneTest.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/GreenstoneTest.java	(revision 21052)
@@ -0,0 +1,70 @@
+package org.greenstone.p2p;
+
+import java.net.URL;
+import java.util.*;
+
+public class GreenstoneTest implements GreenstoneClient
+{ String name;
+  URL    url;
+  
+  public GreenstoneTest(String id, String url)
+  { try {
+      this.url = new URL(url);
+    }
+    catch (java.net.MalformedURLException malEx)
+      { this.url = null;
+      }
+    this.name = id;
+  }
+
+  public void receiveResponse(NameServerAction action, ClientResponse response)
+  {
+    System.out.println("\n----\nReceived a response:");
+    System.out.println(response.toString());
+  }
+
+  public void receiveMessage(String message, Map parameters)
+  {
+  }
+
+  public String getIdentifier()
+  { return this.name;
+  }
+
+  public URL getURL()
+  { return this.url;
+  }
+
+  /**
+   *  arg[0] => identifier for this machine
+   *  arg[1] => a url for this machine
+   *  arg[2] => a url for the GDS server
+   *
+   * e.g. run with:
+   * "java org.greenstone.p2p.GreenstoneTest localhost http://localhost http://www.nzdl.org"
+   */
+  public static void main(String args[])
+  { GreenstoneClient client = new GreenstoneTest(args[0], args[1]);
+    GreenstoneClient gsdl   = new GreenstoneTest("gsdl", "http://www.gsdl.org");
+    
+    URL server;
+    try
+      {
+	server = new URL(args[2]);
+      }
+    catch (java.net.MalformedURLException malEx)
+      { server = null;
+      }
+    
+    GreenstoneInterface gsdlIface = new GreenstoneInterface(gsdl);
+    gsdlIface.registerWithServer(server);
+
+    GreenstoneInterface iface = new GreenstoneInterface(client);
+    iface.registerWithServer(server);
+
+    Map parameters = new HashMap();
+    iface.sendMessage("ListServers", parameters);
+
+    iface.sendMessage("DocDelta", parameters);
+  }
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/ListServersAction.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/ListServersAction.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/ListServersAction.java	(revision 21052)
@@ -0,0 +1,35 @@
+package org.greenstone.p2p;
+
+import java.util.*;
+import java.net.URL;
+
+public class ListServersAction extends AbstractServerAction
+{ public String LISTSERVERS = "ListServers";
+
+  public ListServersAction(NameServer host)
+  { super(host);
+  }
+
+  public String getActionName()
+  { return LISTSERVERS;
+  }
+  
+  public void doAction(URL forwarder, Map parameters)
+  { this.propagateAction(forwarder, parameters);
+  
+    // Build a list of all servers
+    AbstractClientResponse response = new AbstractClientResponse();
+
+    Enumeration servers = this.host.servers();
+    while (servers.hasMoreElements())
+      { URL gsServer = (URL) servers.nextElement();
+	System.out.println("Adding listserver item " + gsServer);
+
+	response.addValue("Server", gsServer.toString());
+      }
+    response.setSuccessful(true);
+
+    // Send it back to the forwarder right now...
+    this.host.receiveResponse(forwarder, response);
+  }
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/NameServer.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/NameServer.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/NameServer.java	(revision 21052)
@@ -0,0 +1,124 @@
+package org.greenstone.p2p;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+public class NameServer
+{ ServerList list;
+  URL        parent;
+  Vector     childServers; // name servers
+  Vector     children;     // greenstone servers
+  String     identity;
+  Map        actionMap;
+ 
+  public NameServer(String identity)
+  { this.list     = new ServerList();
+    this.identity = identity;
+    this.parent   = null;
+    this.children = new Vector();
+    this.childServers = new Vector();
+
+    this.actionMap = new HashMap();
+
+    NameServerAction listServersAction = new ListServersAction(this);
+    this.addAction(listServersAction);
+    NameServerAction defaultAction = new DefaultServerAction(this);
+    this.addAction(defaultAction);
+    
+    // set up registration listener
+    // set up messaging broadcaster
+    // set up messaging listener
+  }
+
+  private void addAction(NameServerAction action)
+  { this.actionMap.put(action.getActionName(), action);
+  }
+
+  private void loadChildren(BufferedReader in)
+  { try 
+      { String child = in.readLine();
+      }
+    catch (IOException ex)
+      {
+      }
+  }
+
+  private void loadServers(BufferedReader in)
+  { try
+      { String child = in.readLine();
+      }
+    catch (IOException io)
+      {
+      }
+  }
+
+  public void sendMessage(URL origin, String actionName, Map parameters)
+  { NameServerAction action = (NameServerAction) this.actionMap.get(actionName);
+    if (action != null)
+      { action.doAction(origin, parameters);
+      }
+    else
+      { System.out.println("Sending action " + actionName + " through <default>");
+
+	// TODO: add action name to the parameter map so that it is not lost...
+	action = (NameServerAction) this.actionMap.get("Default");
+	if (action != null)
+	{ action.doAction(origin, parameters);
+	}
+      }
+    //NameServerAction action = NameServerActionFactory.createAction(action, parameters);
+    //action.doAction();
+  }
+
+  public void receiveResponse(URL responseTo, ClientResponse response)
+  { GreenstoneInterface.receiveResponse(responseTo, response);
+  }
+    
+  private void loadParent()
+  {
+  }
+
+  public String getIdentity()
+  { return this.identity;
+  }
+  
+  public void setParent(URL parent)
+  { this.parent = parent;
+  }
+  
+  public URL getParent()
+  { return this.parent;
+  }
+  
+  public void addSubServer(URL subserver)
+  { this.childServers.add(subserver);
+  }
+  
+  public void registerServer(URL child)
+  { System.out.println("Receiving registration from " + child.toString());
+    this.children.add(child);
+  }
+  
+  public Enumeration subServers()
+  { return this.childServers.elements();
+  }
+  
+  public Enumeration servers()
+  { return this.children.elements();
+  }
+
+  public static boolean pollServer(URL server)
+  { return false;
+  }
+
+  /**
+   *  
+   */
+  public static void main(String args[])
+  { NameServer server = new NameServer(args[0]);
+
+    // Load the action items
+    // Wait for signals
+  }
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/NameServerAction.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/NameServerAction.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/NameServerAction.java	(revision 21052)
@@ -0,0 +1,10 @@
+package org.greenstone.p2p;
+
+import java.util.Map;
+import java.net.URL;
+
+public interface NameServerAction
+{ public String getActionName();
+  public void   doAction(URL forwarder, Map parameters);
+  public void   propagateAction(URL forwarder, Map parameters);
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/NameServerActionFactory.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/NameServerActionFactory.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/NameServerActionFactory.java	(revision 21052)
@@ -0,0 +1,9 @@
+package org.greenstone.p2p;
+
+import java.util.Map;
+
+public class NameServerActionFactory
+{ public NameServerAction createAction(String actionName, Map parameters)
+  { return null;
+  }
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/NameServerRegistrationThread.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/NameServerRegistrationThread.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/NameServerRegistrationThread.java	(revision 21052)
@@ -0,0 +1,16 @@
+package org.greenstone.p2p;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+public class NameServerRegistrationThread extends Thread
+{ public void run() 
+  { /*try
+    {
+    }
+    catch (IOException ex)
+    {
+    }*/
+  }
+}
Index: gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/ServerList.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/ServerList.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/src/org/greenstone/p2p/ServerList.java	(revision 21052)
@@ -0,0 +1,19 @@
+package org.greenstone.p2p;
+
+import java.util.*;
+
+public class ServerList
+{ Map map;
+
+  public ServerList()
+  { this.map = new HashMap();
+  }
+  
+  public void addServer(GreenstoneServer server)
+  { this.map.put(server, server);
+  }
+  
+  Iterator servers()
+  { return ((Collection)this.map.values()).iterator();
+  }
+}
Index: gs3-extensions/gsdl-as/trunk/test-src/org/greenstone/gsdlas/AlertingServiceTest.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/test-src/org/greenstone/gsdlas/AlertingServiceTest.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/test-src/org/greenstone/gsdlas/AlertingServiceTest.java	(revision 21052)
@@ -0,0 +1,163 @@
+/*
+ * Created on Nov 24, 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.io.StringWriter;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.exception.*;
+
+import junit.framework.TestCase;
+
+/**
+ * @author schweer
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class AlertingServiceTest extends TestCase {
+
+    private AlertingService as;
+    
+    public static void main(String[] args) {
+        junit.textui.TestRunner.run(AlertingServiceTest.class);
+    }
+
+    public final void testSubscribe() {
+        try {
+            Velocity.init("/research/schweer/gsdl3/extensions/gsdl-as/build/velocity.properties");
+            VelocityContext context = new VelocityContext();
+            
+            Map arguments = new TreeMap();
+            arguments.put(Constants.TYPE_FIELD, "new_document");
+            arguments.put(Constants.COLLECTION_ID_FIELD, "testing");
+            arguments.put(Constants.DOCUMENT_CONTENT_FIELD, "albatros");
+            
+            String template = as.createSubscription(arguments, context);
+            
+            StringWriter w = new StringWriter();
+            boolean b = Velocity.mergeTemplate(template, 
+                    org.apache.velocity.runtime.RuntimeSingleton.getString(Velocity.INPUT_ENCODING, Velocity.ENCODING_DEFAULT), 
+                    context, 
+                    w);
+            assertTrue(b);
+            System.out.println(w);
+        } catch (ResourceNotFoundException e) {
+            e.printStackTrace();
+            fail("no template");
+        } catch (ParseErrorException e) {
+            e.printStackTrace();
+            fail();
+        } catch (MethodInvocationException e) {
+            e.printStackTrace();
+            fail();
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail();
+        }
+    }
+
+    public final void testListSubscriptions() {
+        try {
+            Velocity.init("/research/schweer/gsdl3/extensions/gsdl-as/build/velocity.properties");
+            VelocityContext context = new VelocityContext();
+            
+            Map arguments = new TreeMap();
+            
+            String template = as.listSubscriptions(arguments, context);
+            
+            StringWriter w = new StringWriter();
+            boolean b = Velocity.mergeTemplate(template, 
+                    org.apache.velocity.runtime.RuntimeSingleton.getString(Velocity.INPUT_ENCODING, Velocity.ENCODING_DEFAULT), 
+                    context, 
+                    w);
+            assertTrue(b);
+            System.out.println(w);
+        } catch (ResourceNotFoundException e) {
+            e.printStackTrace();
+            fail("no template");
+        } catch (ParseErrorException e) {
+            e.printStackTrace();
+            fail();
+        } catch (MethodInvocationException e) {
+            e.printStackTrace();
+            fail();
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail();
+        }
+    }
+
+    public final void testReceiveEvent() {
+        try {
+            Velocity.init("/research/schweer/gsdl3/extensions/gsdl-as/build/velocity.properties");
+            VelocityContext context = new VelocityContext();
+            
+            Map arguments = new TreeMap();
+            arguments.put(Constants.COLLECTION_ID_FIELD, "testing");
+            arguments.put(Constants.TYPE_FIELD, "new_document");
+            arguments.put(Constants.DOCUMENT_ID_FIELD, "20041124:1");
+            arguments.put(Constants.DOCUMENT_TITLE_FIELD, "keine albatrosse in sicht");
+            arguments.put(Constants.HOST_ID_FIELD, "http://localhost:8080/soap/servlet/rpcrouter");
+            
+            as.receiveEvent(arguments);
+            
+        } catch (ResourceNotFoundException e) {
+            e.printStackTrace();
+            fail("no template");
+        } catch (ParseErrorException e) {
+            e.printStackTrace();
+            fail();
+        } catch (MethodInvocationException e) {
+            e.printStackTrace();
+            fail();
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail();
+        }
+    }
+
+    public final void testShowFeed() {
+        try {
+            Velocity.init("/research/schweer/gsdl3/extensions/gsdl-as/build/velocity.properties");
+            VelocityContext context = new VelocityContext();
+            
+            Map arguments = new TreeMap();
+            arguments.put("subscriptionID", "0");
+            
+            String template = as.showFeed(arguments, context);
+            
+            StringWriter w = new StringWriter();
+            boolean b = Velocity.mergeTemplate(template, 
+                    org.apache.velocity.runtime.RuntimeSingleton.getString(Velocity.INPUT_ENCODING, Velocity.ENCODING_DEFAULT), 
+                    context, 
+                    w);
+            assertTrue(b);
+            System.out.println(w);
+        } catch (ResourceNotFoundException e) {
+            e.printStackTrace();
+            fail("no template");
+        } catch (ParseErrorException e) {
+            e.printStackTrace();
+            fail();
+        } catch (MethodInvocationException e) {
+            e.printStackTrace();
+            fail();
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail();
+        }
+    }
+    protected void setUp() throws Exception {
+        as = new AlertingService();
+    }
+}
Index: gs3-extensions/gsdl-as/trunk/test-src/org/greenstone/gsdlas/GreenstoneCommunicatorTest.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/test-src/org/greenstone/gsdlas/GreenstoneCommunicatorTest.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/test-src/org/greenstone/gsdlas/GreenstoneCommunicatorTest.java	(revision 21052)
@@ -0,0 +1,75 @@
+/*
+ * Created on Nov 17, 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.net.URL;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+/**
+ * @author schweer
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class GreenstoneCommunicatorTest extends TestCase {
+
+    private GreenstoneCommunicator gsComm;
+    
+    public static void main(String[] args) {
+        junit.textui.TestRunner.run(GreenstoneCommunicatorTest.class);
+    }
+    
+    protected void setUp() throws Exception {
+        URL host;
+        try {
+            host = new URL("http://localhost:8080/soap/servlet/rpcrouter");
+            gsComm = new GreenstoneCommunicator(host);
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+    
+    public final void testGetSiteList() {
+        String[] sites;
+        try {
+            sites = gsComm.getCollectionNames();
+            for (int i = 0; i < sites.length; i++) {
+                System.out.println(sites[i]);
+                
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+    
+    public final void testSupportsFullTextSearch() {
+        try {
+            assertTrue(gsComm.supportsTextSearch("gs2mgppdemo"));
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+    
+    public final void testFullTextSearch() {
+        try {
+            Set result = gsComm.fullTextSearch("gs2mgppdemo", "Burundi");
+            assertEquals(14, result.size());
+            assertTrue(result.contains("HASH4632a8a51d33c47a75c559.7.1"));
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+}
Index: gs3-extensions/gsdl-as/trunk/test-src/org/greenstone/gsdlas/ProfileStoreTest.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/test-src/org/greenstone/gsdlas/ProfileStoreTest.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/test-src/org/greenstone/gsdlas/ProfileStoreTest.java	(revision 21052)
@@ -0,0 +1,87 @@
+/*
+ * Created on Nov 18, 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.net.URL;
+import java.util.*;
+
+import junit.framework.TestCase;
+
+/**
+ * @author schweer
+ *
+ * TODO To change the template for this generated type comment go to
+ * Window - Preferences - Java - Code Style - Code Templates
+ */
+public class ProfileStoreTest extends TestCase {
+
+    public static void main(String[] args) {
+        junit.textui.TestRunner.run(ProfileStoreTest.class);
+    }
+
+    public final void testAddSubscription() {
+        try {
+            addSubscriptions();
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    public final void testFilter() {
+        try {
+            addSubscriptions();
+            
+            URL url = new URL("http://localhost:8080/soap/servlet/rpcrouter");
+            GreenstoneCommunicator gsComm = new GreenstoneCommunicator(url);
+            
+            Map event = new TreeMap();
+            
+            event.put(Constants.DOCUMENT_ID_FIELD, "20041118:5");
+            event.put(Constants.COLLECTION_ID_FIELD, "gs3mgdemo");
+            event.put(Constants.HOST_ID_FIELD, "http://localhost:8080/soap/servlet/rpcrouter");
+            
+            Set result = ProfileStore.getInstance().filter(event, gsComm);
+            
+            System.out.println(result.size());
+            
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    private void addSubscriptions() throws Exception {
+        Map valueMap = new TreeMap();
+        
+        valueMap.put(Constants.COLLECTION_ID_FIELD, "gs3mgdemo");
+        valueMap.put(Constants.DOCUMENT_CONTENT_FIELD, "Tomatensalat");
+        
+        addSubscription(valueMap);
+        
+        valueMap.clear();
+        
+        valueMap.put(Constants.COLLECTION_ID_FIELD, "gs3mgdemo");
+        valueMap.put(Constants.DOCUMENT_CONTENT_FIELD, "Water+Buffalo");
+        
+        addSubscription(valueMap);
+        
+        valueMap.clear();
+        
+        valueMap.put(Constants.COLLECTION_ID_FIELD, "gs3mgdemo");
+        valueMap.put(Constants.DOCUMENT_CONTENT_FIELD, "gender+equality");
+        
+        addSubscription(valueMap);
+    }
+    
+    private void addSubscription(Map valueMap) throws Exception {  
+        ProfileStore.getInstance().createSubscription(valueMap);    
+    }
+    
+}
Index: gs3-extensions/gsdl-as/trunk/test-src/org/greenstone/gsdlas/util/MailerTest.java
===================================================================
--- gs3-extensions/gsdl-as/trunk/test-src/org/greenstone/gsdlas/util/MailerTest.java	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/test-src/org/greenstone/gsdlas/util/MailerTest.java	(revision 21052)
@@ -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: gs3-extensions/gsdl-as/trunk/web/WEB-INF/web.xml
===================================================================
--- gs3-extensions/gsdl-as/trunk/web/WEB-INF/web.xml	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/web/WEB-INF/web.xml	(revision 21052)
@@ -0,0 +1,122 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+<!DOCTYPE web-app 
+    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" 
+    "http://java.sun.com/dtd/web-app_2_3.dtd">
+
+<web-app>
+
+
+    <!-- General description of your web application -->
+
+    <display-name>Alerting Service for Greenstone</display-name>
+    <description>
+	An alerting service for Greenstone, 
+	written by Andrea Schweer (schweer@cs.waikato.ac.nz).
+    </description>
+
+
+    <!-- Context initialization parameters that define shared
+         String constants used within your application, which
+         can be customized by the system administrator who is
+         installing your application.  The values actually
+         assigned to these parameters can be retrieved in a
+         servlet or JSP page by calling:
+
+             String value =
+               getServletContext().getInitParameter("name");
+
+         where "name" matches the <param-name> element of
+         one of these initialization parameters.
+
+         You can define any number of context initialization
+         parameters, including zero.
+    -->
+
+    <context-param>
+      <param-name>webmaster</param-name>
+      <param-value>schweer@cs.waikato.ac.nz</param-value>
+      <description>
+        The EMAIL address of the administrator to whom questions
+        and comments about this application should be addressed.
+      </description>
+    </context-param>
+
+
+    <!-- Servlet definitions for the servlets that make up
+         your web application, including initialization
+         parameters.  With Tomcat, you can also send requests
+         to servlets not listed here with a request like this:
+
+           http://localhost:8080/{context-path}/servlet/{classname}
+
+         but this usage is not guaranteed to be portable.  It also
+         makes relative references to images and other resources
+         required by your servlet more complicated, so defining
+         all of your servlets (and defining a mapping to them with
+         a servlet-mapping element) is recommended.
+
+         Servlet initialization parameters can be retrieved in a
+         servlet or JSP page by calling:
+
+             String value =
+               getServletConfig().getInitParameter("name");
+
+         where "name" matches the <param-name> element of
+         one of these initialization parameters.
+
+         You can define any number of servlets, including zero.
+    -->
+
+    <servlet>
+      <servlet-name>service</servlet-name>
+      <description>
+        An alerting service for Greenstone 3
+      </description>
+      <servlet-class>org.greenstone.gsdlas.AlertingService</servlet-class>
+      <init-param>
+            <param-name>properties</param-name>
+            <param-value>/velocity.properties</param-value>
+      </init-param>
+    </servlet>
+<!--
+    <servlet>
+      <servlet-name>subscribe</servlet-name>
+      <description>
+        This servlet creates new subscriptions.
+      </description>
+      <servlet-class>org.greenstone.gsdlas.SubscriptionServlet</servlet-class>
+    </servlet> -->
+
+	<!-- Define mappings that are used by the servlet container to
+         translate a particular request URI (context-relative) to a
+         particular servlet.  The examples below correspond to the
+         servlet descriptions above.  Thus, a request URI like:
+
+           http://localhost:8080/{contextpath}/graph
+
+         will be mapped to the "graph" servlet, while a request like:
+
+           http://localhost:8080/{contextpath}/saveCustomer.do
+
+         will be mapped to the "controller" servlet.
+
+         You may define any number of servlet mappings, including zero.
+         It is also legal to define more than one mapping for the same
+         servlet, if you wish to.
+    -->
+
+<!--    <servlet-mapping>
+      <servlet-name>receive</servlet-name>
+      <url-pattern>/receive</url-pattern>
+    </servlet-mapping>
+
+    <servlet-mapping>
+      <servlet-name>subscribe</servlet-name>
+      <url-pattern>/subscribe</url-pattern>
+    </servlet-mapping> -->
+    <servlet-mapping>
+      <servlet-name>service</servlet-name>
+      <url-pattern>/service</url-pattern>
+    </servlet-mapping>
+</web-app>
Index: gs3-extensions/gsdl-as/trunk/web/about.html
===================================================================
--- gs3-extensions/gsdl-as/trunk/web/about.html	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/web/about.html	(revision 21052)
@@ -0,0 +1,20 @@
+<?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>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+      <title>Greenstone Version 3</title>
+			<link rel="stylesheet" type="text/css" href="default.css"/>
+			</head>
+  <body>
+    
+    <!-- page banner (_style:pagebanner_) -->
+    <div id="content"><h3>Alerting Service for Greenstone &ndash; About</h3>
+		<p></p>
+	
+	
+	(description of what an alerting service does)<br/>
+	<a href="index.html">back</a> to the alerting service.<br/>
+	</div>
+  </body></html>
Index: gs3-extensions/gsdl-as/trunk/web/default.css
===================================================================
--- gs3-extensions/gsdl-as/trunk/web/default.css	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/web/default.css	(revision 21052)
@@ -0,0 +1,52 @@
+body {
+	color:             black;
+	background-color:  white;
+	/*font-family:       "Trebuchet MS", Arial, helvetica, sans-serif;*/
+	text-align:        center;
+	margin:            0px;
+	background-image:  url("images/chalk.gif");
+	background-repeat: repeat-y;
+}
+
+#content {
+	background-image: url("images/gsdlhead.gif");
+	background-repeat: no-repeat;
+	background-position: center top;
+	padding-top:  88px;
+	width:        537px;
+	position:     relative;
+	text-align:   left;
+	margin-left:  auto;
+	margin-right: auto;
+	margin-top:   10px;
+}
+
+
+.error {
+        color: red;
+        font-weight: bold;
+}
+
+.details {
+        font-size: small;
+}
+
+table, thead {
+        border: 1pt solid black;
+}
+
+thead {
+        background-color: gray;
+}
+
+a:link {
+	color: #006666;
+}
+
+a:active {
+	color: #cc9900;
+}
+
+a:visited {
+	color: #666633;
+}
Index: gs3-extensions/gsdl-as/trunk/web/descriptor.xml
===================================================================
--- gs3-extensions/gsdl-as/trunk/web/descriptor.xml	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/web/descriptor.xml	(revision 21052)
@@ -0,0 +1,8 @@
+<isd:service
+  xmlns:isd="http://xml.apache.org/xml-soap/deployment"
+  id="alerting" checkMustUnderstands="false">
+  <isd:provider type="java" scope="Session" methods="receiveEvent">
+    <isd:java class="org.greenstone.gsdlas.AlertingService" static="false"/>
+  </isd:provider>
+  <isd:faultListener>org.apache.soap.server.DOMFaultListener</isd:faultListener>
+</isd:service>
Index: gs3-extensions/gsdl-as/trunk/web/feed.css
===================================================================
--- gs3-extensions/gsdl-as/trunk/web/feed.css	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/web/feed.css	(revision 21052)
@@ -0,0 +1,45 @@
+@import url("default.css");
+
+#channelhead {
+	margin-bottom: 20px;
+}
+
+#channelhead a:link {
+	text-decoration: none;
+	color:           black;
+}
+
+#channelhead a:visited {
+	text-decoration: none;
+	color:            black;
+}
+
+
+#channelhead a:hover, a:focus, a:active {
+	color:            blue;
+	text-decoration:  underline;
+}
+
+#channelinfo {
+	border-top: 2px solid black;
+	font-size: small;
+	margin-top: 20px;
+}
+
+div.item {
+	margin: 10px 0px;
+	border: 1px dashed gray;
+	border-left: 10px solid #9CBE9C;
+	padding: 2px;
+	padding-left: 8px;
+	width: auto;
+	background-color: #DCDCDC;
+}
+
+span.date, span.category {
+	font-style: italic;
+}
+
+div.description {
+	font-size: small;
+}
Index: gs3-extensions/gsdl-as/trunk/web/form.css
===================================================================
--- gs3-extensions/gsdl-as/trunk/web/form.css	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/web/form.css	(revision 21052)
@@ -0,0 +1,72 @@
+@import url("default.css");
+
+form {
+	margin: 10px 0px;
+	border: 1px dashed gray;
+	border-left: 10px solid #9CBE9C;
+	padding: 2px;
+	padding-left: 8px;
+	width: auto;
+	background-color: #DCDCDC;
+}
+
+td, th {
+	padding: 0px 5px;
+}
+
+thead tr {
+	background-color: #CCCCCC;
+}
+
+tr.even {
+	background-color: #CCCCCC;
+}
+
+.explanation {
+	font-size: small;
+}
+
+#progress {
+	font-size: x-small;
+	border: 0px solid black;
+	width: 80%;
+	margin: 5px auto;
+}
+
+#progress td {
+	width: 25%;
+	vertical-align: top;
+	text-align: center;
+	color: #555;
+	border-top: 10px solid #CCC;
+	font-family: "Trebuchet MS", Arial, helvetica, sans-serif;
+}
+
+#progress td.completed {
+	border-top-color: #9CBE9C;
+	color: #000;
+}
+
+#progress td.current {
+	background-color: #F0E68C;
+	color: #000;
+}
+
+table.subscription {
+	font-size: small;
+}
+
+.rssbutton {
+	background: #ff6600;
+	color: #ffffff;
+	border-left:   1px solid #cc9966;
+	border-top:    1px solid #ccaa99;
+	border-right:  1px solid #993300;
+	border-bottom: 1px solid #331100;
+	padding: 0px 0.5em 0px 0.5em;
+	font-family: helvetica, arial, sans-serif;
+	font-weight: bold;
+	font-size: xx-small;
+	margin-top: 1em;
+}
+
Index: gs3-extensions/gsdl-as/trunk/web/index.html
===================================================================
--- gs3-extensions/gsdl-as/trunk/web/index.html	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/web/index.html	(revision 21052)
@@ -0,0 +1,32 @@
+<?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>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+		<title>Greenstone Version 3</title>
+		<link rel="stylesheet" type="text/css" href="default.css"/>
+	</head>
+	<body>
+    
+    <!-- page banner (_style:pagebanner_) -->
+		<div id="content">
+			<h3>Alerting Service for Greenstone</h3>
+			<p>What do you want to do today?</p>
+			
+			<ul>
+				<li>
+					<a href="/alerting/service?action=showLoginForm">Log in</a></li>
+				</li>
+				<li>
+					<a href="subscribe.html">Create</a> a subscription.</li>
+				<li>
+					<a href="/alerting/service?action=listSubscriptions">View and edit</a> your existing subscriptions.</li>
+				<li>
+					<a href="about.html">Read more</a> about the alerting service.</li>
+				<li>
+					<a href="/alerting/service?action=logout">Log out</a></li?
+			</ul>
+		</div>
+	</body>
+</html>
Index: gs3-extensions/gsdl-as/trunk/web/list.css
===================================================================
--- gs3-extensions/gsdl-as/trunk/web/list.css	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/web/list.css	(revision 21052)
@@ -0,0 +1,12 @@
+@import url("default.css");
+
+div.subscription {
+	margin: 10px 0px;
+	border: 1px dashed gray;
+	border-left: 10px solid #9CBE9C;
+	padding: 2px;
+	padding-left: 8px;
+	width: auto;
+	background-color: #DCDCDC;
+}
+
Index: gs3-extensions/gsdl-as/trunk/web/rss2html.xsl
===================================================================
--- gs3-extensions/gsdl-as/trunk/web/rss2html.xsl	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/web/rss2html.xsl	(revision 21052)
@@ -0,0 +1,65 @@
+<?xml version="1.0" ?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+	<xsl:output method="xml" 
+  	doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" 
+	  doctype-public="-//W3C//DTD XHTML 1.0 Transitional//EN" indent="yes"/>
+	<!-- <xsl:output method="html"/> -->
+
+	<xsl:template match="/">
+		<html>
+			<xsl:attribute name="lang">
+				<xsl:value-of select="//channel/language"/>
+			</xsl:attribute>
+			<head>
+				<link rel="stylesheet" type="text/css" href="/alerting/feed.css"/>
+				<title>
+					<xsl:value-of select="//channel/title"/>
+				</title>
+			</head>
+			<body>
+				<div id="content">
+					<xsl:apply-templates/>
+				</div>
+   		</body>
+		</html>
+  </xsl:template>
+	
+	<xsl:template match="channel">
+		<div id="channelhead">
+      <h3>
+				<a>
+      		<xsl:attribute name="href">
+        		<xsl:value-of select="link"/>
+					</xsl:attribute>
+					<xsl:value-of select="title"/>
+				</a>
+			</h3>
+			<div class="description">
+        	<xsl:value-of select="description" disable-output-escaping="yes"/>
+			</div>
+		</div>
+    <xsl:apply-templates/>
+		<div id="channelinfo">
+			Last rebuilt <xsl:value-of select="lastBuildDate"/>.<br/>
+			This file has been automatically generated by <xsl:value-of select="generator"/>; please <a><xsl:attribute name="href">mailto:<xsl:value-of select="webMaster"/></xsl:attribute>contact the webmaster</a> if you have any questions.<br/>
+		</div>
+  </xsl:template>
+
+  <xsl:template match="item">
+		<div class="item">
+			<span class="date"><xsl:value-of select="pubDate"/></span>: 
+			<span class="category"><xsl:value-of select="category"/></span><br/>
+    	<a class="item">
+      	<xsl:attribute name="href">
+        	<xsl:value-of select="link"/>
+				</xsl:attribute>
+				<xsl:value-of select="title"/>
+      </a><br/>
+		</div>
+      <xsl:apply-templates/>
+  </xsl:template>
+
+  <xsl:template match="text()"/>
+	
+</xsl:stylesheet>
Index: gs3-extensions/gsdl-as/trunk/web/subscribe.html
===================================================================
--- gs3-extensions/gsdl-as/trunk/web/subscribe.html	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/web/subscribe.html	(revision 21052)
@@ -0,0 +1,30 @@
+<?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>
+		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+		<title>Greenstone Version 3</title>
+		<link rel="stylesheet" type="text/css" href="form.css"/>
+	</head>
+	<body>
+    
+    <!-- page banner (_style:pagebanner_) -->
+		<div id="content">
+			<h3>Alerting Service for Greenstone</h3>
+			<p>Create a subscription</p>
+			
+			You can subscribe to be notified of events about these elements:
+			<ul>
+				<li><a href="/alerting/service?action=createSubscription">documents</a> (new, modified or deleted documents)</li>
+				<li><a href="sub_docparts.html">parts of documents</a> (new, modified or deleted parts of documents)</li>
+				<li><a href="sub_metadata.html">document metadata</a> (new, modified or deleted metadata of documents)</li>
+				<li><a href="sub_collection.html">collections</a> (new, rebuilt or deleted collections)</li>
+				<li><a href="sub_classifiers.html">classifiers of collections</a> (new or deleted classifiers) <img alt="help" src="images/help.png" longdesc="this influences the ways you can browse a collection" title="this influences the ways you can browse a collection"/></li>
+				<li><a href="sub_indexes.html">indexes of a collection</a> (new or deleted indexes) <img alt="help" src="images/help.png" longdesc="this influences the ways you can search in a collection" title="this influences the ways you can search in a collection"/></li>
+			</ul>
+			
+			<a href="/alerting">back</a> to the alerting service
+		</div>
+	</body>
+</html>
Index: gs3-extensions/gsdl-as/trunk/web/test.html
===================================================================
--- gs3-extensions/gsdl-as/trunk/web/test.html	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/web/test.html	(revision 21052)
@@ -0,0 +1,68 @@
+<?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>Alerting Service for Greenstone</title>
+		<link href="default.css" rel="stylesheet" type="text/css"/>
+		<style type="text/css">
+		<!--
+		table { border:1pt solid black; padding:2px; background:#F0F0F0; }
+		th { text-align:left; background:lightblue; }
+		th,td { border-width:0px; }
+		-->
+		</style>
+	</head>
+	<body>
+		<div id="content">
+		<!-- <h1>Test</h1>
+		<form action="/alerting/service?test" method="post">
+			<input type="text" name="text"/><br/>
+			<input type="submit" value="submit"/>
+		</form>
+		<hr/> -->
+		
+		<h3>Subscribe</h3>
+		<form action="/alerting/service" method="post" name="subscribe">
+			<input type="hidden" name="action" value="subscribe"/>
+			<table border="1">
+				<tr>
+					<th>Event Type</th>
+					<td>
+						<input type="radio" name="type" value="new_document" checked="checked"/> new document
+						<input type="radio" name="type" value="document_deleted"/> document deleted
+						<input type="radio" name="type" value="document_content_changed" /> content changed
+						<input type="radio" name="type" value="document_md_changed"/> metadata changed
+					</td>
+				</tr>
+				<tr>
+					<th>Document ID</th>
+					<td>
+						<input type="text" name="documentID"/> 
+					</td>
+				</tr>
+				<tr>
+					<th>Document Title</th>
+					<td>
+						<input type="text" name="document_title"/>
+					</td>
+				</tr>
+				<tr>
+					<th>Document Content</th>
+					<td>
+						<input type="text" name="document_content"/>
+					</td>
+				</tr>
+				<tr>
+					<th>Collection ID</th>
+					<td>
+					 <input type="text" name="collectionID"/>
+					</td>
+				</tr>
+			</table>
+			<br/>
+			<input type="submit" value="submit"/>
+		</form>
+		</div>
+	</body>
+</html>
Index: gs3-extensions/gsdl-as/trunk/web/velocity.properties
===================================================================
--- gs3-extensions/gsdl-as/trunk/web/velocity.properties	(revision 21052)
+++ gs3-extensions/gsdl-as/trunk/web/velocity.properties	(revision 21052)
@@ -0,0 +1,155 @@
+#----------------------------------------------------------------------------
+# These are the default properties for the
+# Velocity Runtime. These values are used when
+# Runtime.init() is called, and when Runtime.init(properties)
+# fails to find the specificed properties file.
+#----------------------------------------------------------------------------
+
+#----------------------------------------------------------------------------
+# R U N T I M E  L O G
+#----------------------------------------------------------------------------
+
+#----------------------------------------------------------------------------
+#  default LogSystem to use: default: AvalonLogSystem
+#----------------------------------------------------------------------------
+
+# runtime.log.logsystem.class = org.apache.velocity.runtime.log.AvalonLogSystem,org.apache.velocity.runtime.log.SimpleLog4JLogSystem
+
+runtime.log.logsystem.class = org.apache.velocity.runtime.log.SimpleLog4JLogSystem
+
+#---------------------------------------------------------------------------
+# This is the location of the Velocity Runtime log.
+#----------------------------------------------------------------------------
+
+runtime.log = ../logs/velocity.log
+
+#----------------------------------------------------------------------------
+# This controls if Runtime.error(), info() and warn() messages include the
+# whole stack trace. The last property controls whether invalid references
+# are logged.
+#----------------------------------------------------------------------------
+
+runtime.log.error.stacktrace = true
+runtime.log.warn.stacktrace = false
+runtime.log.info.stacktrace = false
+runtime.log.invalid.reference = true
+
+#----------------------------------------------------------------------------
+# Configuration for the Log4JLogSystem.
+# You must define the runtime.log.logsystem.class property to be:
+#   org.apache.velocity.runtime.log.Log4JLogSystem
+#
+# You must also include Log4J's .jar files into your classpath. They are
+# included with the Velocity distribution in the build/lib directory.
+#
+# There are several different options that you can configure.
+# Uncomment the ones that you want and also define their settings.
+#----------------------------------------------------------------------------
+#runtime.log.logsystem.log4j.pattern=%d - %m%n
+#runtime.log.logsystem.log4j.file.size=100000
+#runtime.log.logsystem.log4j.file.backups=1
+#runtime.log.logsystem.log4j.syslogd.host=my.syslog.server.com
+#runtime.log.logsystem.log4j.syslogd.facility=LOG_DAEMON
+#runtime.log.logsystem.log4j.remote.host=my.remote.server.com
+#runtime.log.logsystem.log4j.remote.port=1099
+#runtime.log.logsystem.log4j.email.server=localhost
+#runtime.log.logsystem.log4j.email.from=root@localhost
+#runtime.log.logsystem.log4j.email.to=root@localhost
+#runtime.log.logsystem.log4j.email.subject=Velocity Error Report
+#runtime.log.logsystem.log4j.email.buffer.size=512
+
+#----------------------------------------------------------------------------
+# T E M P L A T E  E N C O D I N G
+#----------------------------------------------------------------------------
+
+input.encoding=ISO-8859-1
+output.encoding=ISO-8859-1
+
+#----------------------------------------------------------------------------
+# F O R E A C H  P R O P E R T I E S
+#----------------------------------------------------------------------------
+# These properties control how the counter is accessed in the #foreach
+# directive. By default the reference $velocityCount will be available
+# in the body of the #foreach directive. The default starting value
+# for this reference is 1.
+#----------------------------------------------------------------------------
+
+directive.foreach.counter.name = velocityCount
+directive.foreach.counter.initial.value = 1
+
+#----------------------------------------------------------------------------
+# I N C L U D E  P R O P E R T I E S
+#----------------------------------------------------------------------------
+# These are the properties that governed the way #include'd content
+# is governed.
+#----------------------------------------------------------------------------
+
+directive.include.output.errormsg.start = <!-- include error :
+directive.include.output.errormsg.end   =  see error log -->
+
+#----------------------------------------------------------------------------
+# P A R S E  P R O P E R T I E S
+#----------------------------------------------------------------------------
+
+directive.parse.max.depth = 10
+
+#----------------------------------------------------------------------------
+# T E M P L A T E  L O A D E R S
+#----------------------------------------------------------------------------
+#
+#
+#----------------------------------------------------------------------------
+
+# resource.loader = file
+# 
+# file.resource.loader.description = Velocity File Resource Loader
+# file.resource.loader.class = org.apache.velocity.runtime.resource.loader.FileResourceLoader
+# file.resource.loader.path = .
+# file.resource.loader.cache = false
+# file.resource.loader.modificationCheckInterval = 2
+resource.loader = class
+class.resource.loader.description = Velocity Class Resource Loader
+class.resource.loader.class = org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader
+
+#----------------------------------------------------------------------------
+# VELOCIMACRO PROPERTIES
+#----------------------------------------------------------------------------
+# global : name of default global library.  It is expected to be in the regular
+# template path.  You may remove it (either the file or this property) if
+# you wish with no harm.
+#----------------------------------------------------------------------------
+velocimacro.library = VM_global_library.vm
+
+velocimacro.permissions.allow.inline = true
+velocimacro.permissions.allow.inline.to.replace.global = false
+velocimacro.permissions.allow.inline.local.scope = false
+
+velocimacro.context.localscope = false
+
+#----------------------------------------------------------------------------
+# INTERPOLATION
+#----------------------------------------------------------------------------
+# turn off and on interpolation of references and directives in string
+# literals.  ON by default :)
+#----------------------------------------------------------------------------
+runtime.interpolate.string.literals = true
+
+
+#----------------------------------------------------------------------------
+# RESOURCE MANAGEMENT
+#----------------------------------------------------------------------------
+# Allows alternative ResourceManager and ResourceCache implementations
+# to be plugged in.
+#----------------------------------------------------------------------------
+resource.manager.class = org.apache.velocity.runtime.resource.ResourceManagerImpl
+resource.manager.cache.class = org.apache.velocity.runtime.resource.ResourceCacheImpl
+
+#----------------------------------------------------------------------------
+# PLUGGABLE INTROSPECTOR
+#----------------------------------------------------------------------------
+# Allows alternative introspection and all that can of worms brings
+#----------------------------------------------------------------------------
+
+runtime.introspector.uberspect = org.apache.velocity.util.introspection.UberspectImpl
+
+
