Browse Source

Remove stray print statements

Damian Steer 8 years ago
commit
a9dd96a0a2
100 changed files with 17880 additions and 0 deletions
  1. 2 0
      .hgignore
  2. 13 0
      INSTALL
  3. 33 0
      LICENSE
  4. 58 0
      caboto-web-cas/pom.xml
  5. 26 0
      caboto-web-cas/src/main/java/org/caboto/security/cas/UserDetailsServiceImpl.java
  6. 3 0
      caboto-web-cas/src/main/resources/cas.properties
  7. 14 0
      caboto-web-cas/src/main/resources/profiles.xml
  8. 3 0
      caboto-web-cas/src/main/resources/realm.properties
  9. 15 0
      caboto-web-cas/src/main/resources/sdb.ttl
  10. 5 0
      caboto-web-cas/src/main/resources/startup.properties
  11. 137 0
      caboto-web-cas/src/main/webapp/WEB-INF/spring/caboto-context.xml
  12. 46 0
      caboto-web-cas/src/main/webapp/WEB-INF/web.xml
  13. 84 0
      caboto-web-cas/src/main/webapp/index.jsp
  14. 287 0
      caboto-web-cas/src/main/webapp/js/annotations.js
  15. 4221 0
      caboto-web-cas/src/main/webapp/js/prototype.js
  16. 2 0
      caboto-web-cas/src/main/webapp/logout.jsp
  17. 1 0
      caboto-web-cas/src/main/webapp/secured/index.jsp
  18. 72 0
      caboto-web-cas/src/main/webapp/style.css
  19. 48 0
      caboto-web/pom.xml
  20. 14 0
      caboto-web/src/main/resources/profiles.xml
  21. 3 0
      caboto-web/src/main/resources/realm.properties
  22. 15 0
      caboto-web/src/main/resources/sdb.ttl
  23. 5 0
      caboto-web/src/main/resources/startup.properties
  24. 112 0
      caboto-web/src/main/webapp/WEB-INF/spring/caboto-context.xml
  25. 46 0
      caboto-web/src/main/webapp/WEB-INF/web.xml
  26. 83 0
      caboto-web/src/main/webapp/index.jsp
  27. 287 0
      caboto-web/src/main/webapp/js/annotations.js
  28. 4221 0
      caboto-web/src/main/webapp/js/prototype.js
  29. 2 0
      caboto-web/src/main/webapp/logout.jsp
  30. 1 0
      caboto-web/src/main/webapp/secured/index.jsp
  31. 72 0
      caboto-web/src/main/webapp/style.css
  32. 183 0
      caboto/checkstyle.xml
  33. 231 0
      caboto/pom.xml
  34. 1 0
      caboto/src/main/filters/filter.properties
  35. 138 0
      caboto/src/main/java/org/caboto/CabotoJsonSupport.java
  36. 182 0
      caboto/src/main/java/org/caboto/CabotoUtility.java
  37. 551 0
      caboto/src/main/java/org/caboto/OpAsQuery.java
  38. 65 0
      caboto/src/main/java/org/caboto/RdfMediaType.java
  39. 71 0
      caboto/src/main/java/org/caboto/dao/AnnotationDao.java
  40. 267 0
      caboto/src/main/java/org/caboto/dao/AnnotationDaoImpl.java
  41. 204 0
      caboto/src/main/java/org/caboto/domain/Annotation.java
  42. 25 0
      caboto/src/main/java/org/caboto/domain/AnnotationException.java
  43. 144 0
      caboto/src/main/java/org/caboto/domain/AnnotationFactory.java
  44. 47 0
      caboto/src/main/java/org/caboto/filters/AnnotationFilter.java
  45. 97 0
      caboto/src/main/java/org/caboto/filters/AnnotationFilterBase.java
  46. 78 0
      caboto/src/main/java/org/caboto/filters/AnnotationFilterFactory.java
  47. 27 0
      caboto/src/main/java/org/caboto/filters/LarqAnnotationFilter.java
  48. 87 0
      caboto/src/main/java/org/caboto/filters/PropValAnnotationFilter.java
  49. 67 0
      caboto/src/main/java/org/caboto/filters/TypeAnnotationFilter.java
  50. 70 0
      caboto/src/main/java/org/caboto/profile/Profile.java
  51. 81 0
      caboto/src/main/java/org/caboto/profile/ProfileEntry.java
  52. 46 0
      caboto/src/main/java/org/caboto/profile/ProfileRepository.java
  53. 46 0
      caboto/src/main/java/org/caboto/profile/ProfileRepositoryException.java
  54. 230 0
      caboto/src/main/java/org/caboto/profile/ProfileRepositoryXmlImpl.java
  55. 112 0
      caboto/src/main/java/org/caboto/rest/providers/JenaModelRdfProvider.java
  56. 86 0
      caboto/src/main/java/org/caboto/rest/providers/JenaResourceRdfProvider.java
  57. 52 0
      caboto/src/main/java/org/caboto/rest/providers/SPARQLResult.java
  58. 121 0
      caboto/src/main/java/org/caboto/rest/resources/AboutResource.java
  59. 207 0
      caboto/src/main/java/org/caboto/rest/resources/PersonPublicAnnotationResource.java
  60. 124 0
      caboto/src/main/java/org/caboto/rest/resources/SPARQL.java
  61. 73 0
      caboto/src/main/java/org/caboto/rest/resources/VersionResource.java
  62. 46 0
      caboto/src/main/java/org/caboto/security/GateKeeper.java
  63. 65 0
      caboto/src/main/java/org/caboto/security/sparql/Dereifier.java
  64. 52 0
      caboto/src/main/java/org/caboto/security/sparql/GateKeeperEnforcer.java
  65. 39 0
      caboto/src/main/java/org/caboto/security/sparql/GateKeeperFilter.java
  66. 23 0
      caboto/src/main/java/org/caboto/security/sparql/GateKeeperFilterFactory.java
  67. 87 0
      caboto/src/main/java/org/caboto/security/spring/ExitGuard.java
  68. 135 0
      caboto/src/main/java/org/caboto/security/spring/GateKeeperImpl.java
  69. 110 0
      caboto/src/main/java/org/caboto/security/spring/ResourceAccessDecisionVoter.java
  70. 220 0
      caboto/src/main/java/org/caboto/validation/AnnotationValidatorImpl.java
  71. 74 0
      caboto/src/main/java/org/caboto/vocabulary/Annotea.java
  72. 8 0
      caboto/src/main/resources/messages.properties
  73. 14 0
      caboto/src/main/resources/profiles.xml
  74. 15 0
      caboto/src/main/resources/sdb.ttl
  75. 23 0
      caboto/src/main/resources/sparql/findAnnotation.rql
  76. 1 0
      caboto/src/main/resources/version.properties
  77. 305 0
      caboto/src/test/java/org/caboto/CabotoUtilityTest.java
  78. 51 0
      caboto/src/test/java/org/caboto/OpAsQueryTest.java
  79. 206 0
      caboto/src/test/java/org/caboto/dao/AnnotationDaoImplTest.java
  80. 147 0
      caboto/src/test/java/org/caboto/filters/PropValAnnotationFilterTest.java
  81. 29 0
      caboto/src/test/java/org/caboto/filters/TypeAnnotationFilterTest.java
  82. 101 0
      caboto/src/test/java/org/caboto/profile/MockProfileRepositoryImpl.java
  83. 69 0
      caboto/src/test/java/org/caboto/profile/ProfileRepositoryXmlImplTest.java
  84. 209 0
      caboto/src/test/java/org/caboto/rest/resources/AboutResourceSecurityTest.java
  85. 149 0
      caboto/src/test/java/org/caboto/rest/resources/AboutResourceTest.java
  86. 328 0
      caboto/src/test/java/org/caboto/rest/resources/AbstractResourceTest.java
  87. 75 0
      caboto/src/test/java/org/caboto/rest/resources/BasicAuthenticationClientFilter.java
  88. 170 0
      caboto/src/test/java/org/caboto/rest/resources/PersonPrivateAnnotationSecurityTest.java
  89. 218 0
      caboto/src/test/java/org/caboto/rest/resources/PersonPrivateAnnotationTest.java
  90. 194 0
      caboto/src/test/java/org/caboto/rest/resources/PersonPublicAnnotationSecurityTest.java
  91. 220 0
      caboto/src/test/java/org/caboto/rest/resources/PersonPublicAnnotationTest.java
  92. 215 0
      caboto/src/test/java/org/caboto/rest/resources/SPARQLTest.java
  93. 76 0
      caboto/src/test/java/org/caboto/rest/resources/VersionResourceTest.java
  94. 160 0
      caboto/src/test/java/org/caboto/security/GateKeeperTest.java
  95. 44 0
      caboto/src/test/java/org/caboto/security/sparql/GateKeeperEnforcerTest.java
  96. 224 0
      caboto/src/test/java/org/caboto/validation/AnnotationValidationTest.java
  97. 52 0
      caboto/src/test/resources/caboto-context.xml
  98. 67 0
      caboto/src/test/resources/caboto-security.xml
  99. 15 0
      caboto/src/test/resources/sdb-test.ttl
  100. 0 0
      caboto/src/test/resources/startup.properties

+ 2 - 0
.hgignore

@@ -0,0 +1,2 @@
+target
+derby.log

+ 13 - 0
INSTALL

@@ -0,0 +1,13 @@
+To build caboto:
+
+   mvn install
+
+To run the test web application:
+
+   cd crew-web
+   mvn jetty:run
+
+Point your web browser at <http://localhost:9090/caboto-web/>. The displayed web page includes
+the details of a number of test accounts that you can use to create annotations.
+
+See <http://code.google.com/p/caboto/wiki/CompileAndRun> for more details.

+ 33 - 0
LICENSE

@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2008, University of Bristol
+ * Copyright (c) 2008, University of Manchester
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1) Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ * 2) Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3) Neither the names of the University of Bristol and the
+ *    University of Manchester nor the names of their
+ *    contributors may be used to endorse or promote products derived from this
+ *    software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */

+ 58 - 0
caboto-web-cas/pom.xml

@@ -0,0 +1,58 @@
+<?xml version="1.0"?>
+<project>
+    <parent>
+        <artifactId>caboto-parent</artifactId>
+        <groupId>org.caboto</groupId>
+        <version>0.10-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.caboto</groupId>
+    <artifactId>caboto-web-cas</artifactId>
+    <packaging>war</packaging>
+    <name>caboto-web-cas Maven Webapp</name>
+    <url>http://maven.apache.org</url>
+    <profiles>
+        <profile>
+            <id>development</id>
+        </profile>
+    </profiles>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.mortbay.jetty</groupId>
+                <artifactId>maven-jetty-plugin</artifactId>
+                <configuration>
+                    <connectors>
+                        <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
+                            <port>9090</port>
+                            <maxIdleTime>60000</maxIdleTime>
+                        </connector>
+                    </connectors>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <dependencies>
+        <dependency>
+            <groupId>org.caboto</groupId>
+            <artifactId>caboto</artifactId>
+            <version>${caboto.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-taglibs</artifactId>
+            <version>${spring.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-cas-client</artifactId>
+            <version>${spring.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>2.5</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>

+ 26 - 0
caboto-web-cas/src/main/java/org/caboto/security/cas/UserDetailsServiceImpl.java

@@ -0,0 +1,26 @@
+package org.caboto.security.cas;
+
+import org.springframework.dao.DataAccessException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.authority.GrantedAuthorityImpl;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class UserDetailsServiceImpl implements UserDetailsService {
+
+    final static GrantedAuthority auth = new GrantedAuthorityImpl("ROLE_USER");
+
+    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException, DataAccessException {
+
+        List<GrantedAuthority> l = new ArrayList<GrantedAuthority>();
+        l.add(auth);
+
+        return new User(s, "null", true, true, true, true, l);
+    }
+}

+ 3 - 0
caboto-web-cas/src/main/resources/cas.properties

@@ -0,0 +1,3 @@
+cas.login=https://cas/login
+cas.ticket=https://cas/
+cas.service=http://localhost:9090/caboto-web/j_spring_cas_security_check

+ 14 - 0
caboto-web-cas/src/main/resources/profiles.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<profiles>
+    <profile id="SimpleComment" type="http://caboto.org/schema/annotations#SimpleComment">
+        <profileEntry id="title"
+                      propertyType="http://purl.org/dc/elements/1.1/title"
+                      objectDatatype="String"
+                      required="true" />
+        <profileEntry id="description"
+                      objectDatatype="String"
+                      propertyType="http://purl.org/dc/elements/1.1/description"
+                      required="true" />
+    </profile>
+
+</profiles>

+ 3 - 0
caboto-web-cas/src/main/resources/realm.properties

@@ -0,0 +1,3 @@
+user1: secret123,USERS
+user2: secret123,USERS
+admin: admin123,USERS,ADMIN

+ 15 - 0
caboto-web-cas/src/main/resources/sdb.ttl

@@ -0,0 +1,15 @@
+@prefix rdfs:   <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix rdf:    <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix ja:     <http://jena.hpl.hp.com/2005/11/Assembler#> .
+@prefix sdb:    <http://jena.hpl.hp.com/2007/sdb#> .
+
+_:c rdf:type sdb:SDBConnection ;
+    sdb:sdbType     "derby" ;
+    sdb:sdbName     "target/DB/SDB2" ;
+    sdb:driver      "org.apache.derby.jdbc.EmbeddedDriver" ;
+    .
+
+[] rdf:type sdb:Store ;
+   sdb:layout        "layout2" ;
+   sdb:connection     _:c ;
+   .

+ 5 - 0
caboto-web-cas/src/main/resources/startup.properties

@@ -0,0 +1,5 @@
+# DO NOT CHANGE THIS VALUE (unless you know what you are doing)! :-p
+#
+# This property is used by the "StartUp" servlet to  determine if the underlying relational
+# database has been formatted for the RDF store.
+sdb.store.formatted=false

+ 137 - 0
caboto-web-cas/src/main/webapp/WEB-INF/spring/caboto-context.xml

@@ -0,0 +1,137 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:security="http://www.springframework.org/schema/security"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+                           http://www.springframework.org/schema/security
+                           http://www.springframework.org/schema/security/spring-security-3.0.xsd
+                           http://www.springframework.org/schema/context
+                           http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
+        <property name="locations">
+            <list>
+                <value>classpath:cas.properties</value>
+            </list>
+        </property>
+    </bean>
+
+    <context:component-scan base-package="org.caboto"/>
+
+    <!-- repository gives access to the profile of allowed annotations -->
+    <bean id="profileRepository" class="org.caboto.profile.ProfileRepositoryXmlImpl">
+        <constructor-arg index="0" value="profiles.xml"/>
+    </bean>
+
+    <!-- factory creates SDB stores: provides access to the RDF store -->
+
+    <bean id="database" class="org.caboto.jena.db.impl.SDBDatabase">
+        <constructor-arg index="0" value="/sdb.ttl"/>
+    </bean>
+
+    <!-- DAO class for creating and retrieving annotations -->
+
+    <bean id="annotationDao" class="org.caboto.dao.AnnotationDaoImpl" scope="prototype">
+        <constructor-arg index="0" ref="profileRepository"/>
+        <constructor-arg index="1" ref="database"/>
+    </bean>
+
+    <!-- utility class for converting RDF annotations to JSON -->
+
+    <bean id="jsonSupport" class="org.caboto.CabotoJsonSupport"/>
+
+    <!-- SECURITY -->
+
+    <!-- gate keeper : authority on who can see what -->
+
+    <bean id="gatekeeper" class="org.caboto.security.spring.GateKeeperImpl">
+        <constructor-arg index="0" value="ROLE_ADMIN"/>
+    </bean>
+
+    <!-- aop implementation to filter results -->
+
+    <bean id="exitGuard" class="org.caboto.security.spring.ExitGuard">
+        <constructor-arg index="0" ref="gatekeeper"/>
+    </bean>
+
+    <bean id="annotationDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
+        <property name="proxyInterfaces" value="org.caboto.dao.AnnotationDao"/>
+        <property name="interceptorNames" value="exitGuard"/>
+        <property name="target" ref="annotationDao"/>
+    </bean>
+
+    <!-- cas configurations -->
+
+    <bean id="userDetails" class="org.caboto.security.cas.UserDetailsServiceImpl"/>
+
+    <security:http entry-point-ref="casAuthenticationEntryPoint" auto-config="true" path-type="regex"
+                   lowercase-comparisons="false">
+        <security:anonymous username="anonymous" granted-authority="ROLE_ANONYMOUS"/>
+        <security:intercept-url pattern="^.*/public/.*$" method="POST" access="IS_AUTHENTICATED_FULLY"/>
+        <security:intercept-url pattern="^.*/public/.*$" method="DELETE" access="IS_AUTHENTICATED_FULLY"/>
+        <security:intercept-url pattern="^.*/private/.*$" method="GET" access="IS_AUTHENTICATED_FULLY"/>
+        <security:intercept-url pattern="^.*/private/.*$" method="POST" access="IS_AUTHENTICATED_FULLY"/>
+        <security:intercept-url pattern="^.*/private/.*$" method="DELETE" access="IS_AUTHENTICATED_FULLY"/>
+        <security:intercept-url pattern="^.*/secured/.*$" method="GET" access="IS_AUTHENTICATED_FULLY"/>
+        <security:intercept-url pattern=".*" method="GET" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
+        <security:custom-filter position="CAS_FILTER" ref="casAuthenticationFilter"/>
+    </security:http>
+
+    <bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties">
+        <property name="service" value="${cas.service}"/>
+        <property name="sendRenew" value="false"/>
+    </bean>
+
+    <security:authentication-manager alias="authenticationManager">
+        <security:authentication-provider ref="casAuthenticationProvider"/>
+    </security:authentication-manager>
+
+    <bean id="casAuthenticationFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter">
+        <property name="authenticationManager" ref="authenticationManager"/>
+    </bean>
+
+    <bean id="casAuthenticationEntryPoint" class="org.springframework.security.cas.web.CasAuthenticationEntryPoint">
+        <property name="loginUrl" value="${cas.login}"/>
+        <property name="serviceProperties" ref="serviceProperties"/>
+        <property name="encodeServiceUrlWithSessionId" value="false"/>
+    </bean>
+
+    <bean id="casAuthenticationProvider"
+          class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
+        <property name="userDetailsService" ref="userDetails"/>
+        <property name="serviceProperties" ref="serviceProperties"/>
+        <property name="ticketValidator">
+            <bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
+                <constructor-arg index="0" value="${cas.ticket}"/>
+            </bean>
+        </property>
+        <property name="key" value="cas"/>
+    </bean>
+
+    <!-- voter for resources -->
+
+    <bean id="resourceVoter" class="org.caboto.security.spring.ResourceAccessDecisionVoter">
+        <constructor-arg index="0" ref="gatekeeper"/>
+        <constructor-arg index="1" value="annotation"/>
+    </bean>
+
+    <bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter"/>
+
+    <bean id="authenticatedVoter" class="org.springframework.security.access.vote.AuthenticatedVoter"/>
+
+    <!-- custom decision manager -->
+
+    <bean id="decisionManager" class="org.springframework.security.access.vote.UnanimousBased">
+        <property name="allowIfAllAbstainDecisions" value="true"/>
+        <property name="decisionVoters">
+            <list>
+                <ref local="roleVoter"/>
+                <ref local="authenticatedVoter"/>
+                <ref local="resourceVoter"/>
+            </list>
+        </property>
+    </bean>
+
+</beans>

+ 46 - 0
caboto-web-cas/src/main/webapp/WEB-INF/web.xml

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
+                             http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
+
+    <display-name>Caboto</display-name>
+
+
+    <filter>
+        <filter-name>springSecurityFilterChain</filter-name>
+        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
+    </filter>
+
+    <filter-mapping>
+        <filter-name>springSecurityFilterChain</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+
+    <context-param>
+        <param-name>contextConfigLocation</param-name>
+        <param-value>/WEB-INF/spring/caboto-context.xml</param-value>
+    </context-param>
+
+    <listener>
+        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+    </listener>
+
+    <servlet>
+        <servlet-name>Jersey Spring Integration</servlet-name>
+        <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
+        <init-param>
+            <param-name>com.sun.jersey.config.property.packages</param-name>
+            <param-value>org.caboto.rest</param-value>
+        </init-param>
+        <load-on-startup>1</load-on-startup>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>Jersey Spring Integration</servlet-name>
+        <url-pattern>/annotation/*</url-pattern>
+    </servlet-mapping>
+
+
+</web-app>

+ 84 - 0
caboto-web-cas/src/main/webapp/index.jsp

@@ -0,0 +1,84 @@
+<?xml version="1.0"?>
+<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:security="http://www.springframework.org/security/tags">
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <meta name="caboto-annotation" content="http://caboto.org"/>
+    <style type="text/css" media="screen">@import "./style.css";</style>
+    <script type="text/javascript" src="./js/prototype.js"></script>
+    <script type="text/javascript" src="./js/annotations.js"></script>
+    <title>Caboto Test Example</title>
+</head>
+<body onload="initializeAnnotations();">
+
+<%--
+<security:authorize ifAnyGranted="ROLE_USER,ROLE_ADMIN">
+    <h3 class="logout"><a href="./logout.jsp">Logout</a></h3>
+</security:authorize>
+--%>
+
+<%
+    Cookie uid = new Cookie("uid", null);
+    Cookie admin = new Cookie("admin", null);
+
+    if (request.getUserPrincipal() != null) {
+        uid.setValue(request.getUserPrincipal().getName());
+        if (request.isUserInRole("ADMIN")) {
+            admin.setValue("true");
+        }
+    }
+
+    response.addCookie(uid);
+    response.addCookie(admin);
+%>
+
+<div class="annotations">
+
+    <fieldset class="fieldSet">
+        <legend>Annotations</legend>
+        <div id="annotations-results"><p>Sorry, you need a JavaScript enabled browser.</p></div>
+
+
+        <p>Add your own annotation...</p>
+
+        <div id="annotation-messages"></div>
+
+        <%-- show form if they are logged in --%>
+        <security:authorize ifAnyGranted="ROLE_USER,ROLE_ADMIN">
+            <form id="annotation-comment-form"
+                  action="javascript:processForm('<%=request.getUserPrincipal().getName()%>')"
+                  method="post">
+                <p>
+                    <label><strong>Title:</strong></label><br/>
+                    <input id="annotation-title" type="text" name="title" size="50"/><br/>
+                    <label><strong>Description:</strong></label><br/>
+                    <textarea id="annotation-description1" rows="5" cols="50"
+                              name="description"></textarea><br/>
+                    <label><strong>Description (repeated field):</strong></label><br/>
+                    <textarea id="annotation-description2" rows="5" cols="50"
+                              name="description"></textarea><br/>
+                    <input type="radio" name="privacy" value="public" checked="checked"> Public
+                    <input type="radio" name="privacy" value="private"> Private
+                    <input type="hidden" name="type" value="SimpleComment"/>
+                    <input type="hidden" name="annotates" value="http://caboto.org"/><br />
+                    <input id="annotation-submit" type="submit" name="submit" value="Submit"
+                           disabled="disabled"/>
+                </p>
+            </form>
+        </security:authorize>
+
+        <%-- message if not logged in --%>
+        <security:authorize ifNotGranted="ROLE_USER,ROLE_ADMIN">
+            <p>You must be <a href="secured/">logged in</a> to add an annotation.</p>
+
+            <p>Authentication uses a CAS Single Sign-on Service</p>
+
+        </security:authorize>
+
+    </fieldset>
+</div>
+
+</body>
+</html>

+ 287 - 0
caboto-web-cas/src/main/webapp/js/annotations.js

@@ -0,0 +1,287 @@
+var META_TAG_CABOTO = "caboto-annotation";
+var APPLICATION_JSON = "application/json";
+
+function displayMessage(message) {
+    document.getElementById("annotations-results").innerHTML = message;
+}
+
+function createParams(uri) {
+
+    var params = "id=" + encodeURIComponent(uri);
+
+    // extra parameter to stop IE caching the ajax requests
+    if (Prototype.Browser.IE) {
+        params += "&ie-cache-hack=" + new Date().getTime();
+    }
+
+    return params;
+}
+
+function annotationFailure(fail) {
+
+    var error;
+
+    if (fail.status == 404) {
+        error = "<p>There are currently no annotations.</p>";
+    } else if (fail.status == 401) {
+        error = "<p>You are not authorized to view the annotations.</p>";
+    } else {
+        error = "<p>" + fail.status + " - something has gone wrong!";
+    }
+
+    displayMessage(error);
+}
+
+
+function getCookie(name) {
+
+    var cookie = null;
+
+    var allCookies = document.cookie;
+
+    var cookies = allCookies.split(";");
+
+    for (var i = 0; i < cookies.length; i++) {
+
+        var tmp = cookies[i].replace(/^\s+|\s+$/g, '').split("=");
+
+        if (tmp[0] == name) {
+            cookie = tmp[1];
+            break;
+        }
+    }
+
+    return cookie;
+}
+
+/*
+ Parses date strings that are in xsd:dateTime format.
+ */
+function parseDate(dateString) {
+
+    var d = new Date(dateString.substr(0, 19).replace(/-/g, '/').replace("T", " "));
+
+    d.setMinutes(d.getMinutes() + d.getTimezoneOffset());
+
+    var timeZone = dateString.substr(19).split(":");
+    timeZone = timeZone[0] * 60 + (timeZone[1] * 1);
+    d.setMinutes(d.getMinutes() + timeZone);
+
+    return d;
+}
+
+/*
+ Takes the URI that represents the person and returns just the username portion.
+ */
+function parseAuthor(author) {
+    var temp = author.split("/");
+    return temp[temp.length - 2];
+}
+
+function findType(aUri) {
+    var temp = aUri.split("/");
+    return temp[temp.length - 2];
+}
+
+function formatAnnotation(annotation, uid, admin) {
+
+	var date = parseDate(annotation.created);
+    var author = parseAuthor(annotation.author);
+//    var body = annotation.body.description.replace(/\n|\r/g, "<br />\n");
+    var type = findType(annotation.id);
+    
+    var body = "";
+    for(i=0; i<annotation.body.description.length; i++) {
+    	body += annotation.body.description[i] + "<br/>";
+    }
+
+    var output = "<div class='annotation-entry'>";
+
+    if (type == "private") {
+        output += "<div class='annotation-entry-title-private'>"
+    } else {
+        output += "<div class='annotation-entry-title-public'>"
+    }
+
+    output += annotation.body.title + "</div>" +
+              "<div class='annotation-entry-description'>" + body + "</div>" +
+              "<div><div class='annotation-entry-created'>" +
+              "Created on " + date.toLocaleString() + " by " + author + "</div>";
+
+    if (uid == author || admin == "true") {
+        output += "<div style='float: right'>[<a href='#' onclick='deleteAnnotation(\"" +
+                  annotation.id + "\")'>Delete</a>]</div>";
+    }
+
+    output += "</div>";
+
+    output += "</div>";
+
+    return output;
+}
+
+
+function clearForm() {
+
+    if (document.getElementById('annotation-comment-form')) {
+        Form.Element.enable('annotation-submit');
+        Form.Element.clear('annotation-title');
+        Form.Element.clear('annotation-description1');
+        Form.Element.clear('annotation-description2');
+    }
+    document.getElementById("annotation-messages").innerHTML = "";
+}
+
+/*
+ Display annotations - they are received in JSON format.
+ */
+function displayAnnotations(transport) {
+
+    var uid = getCookie("uid");
+    var admin = getCookie("admin");
+
+    var json = transport.responseText.evalJSON(true);
+
+    var output = "";
+
+    if (json.length > 0) {
+
+        json.sort(function(a, b) {
+            return parseDate(a.created) - parseDate(b.created);
+        });
+
+        for (var i = 0; i < json.length; i++) {
+            output += formatAnnotation(json[i], uid, admin);
+        }
+    } else {
+        output = "<p>There are no annotations.</p>";
+    }
+
+
+    displayMessage(output);
+    clearForm();
+}
+
+
+/*
+ Ajax call - find annotations that are about the "uri".
+ */
+function findAnnotations() {
+
+    var annoUri = "";
+
+    // find the URI of the thing we are interested in
+    var meta = document.getElementsByTagName("META");
+
+    for (var i = 0; i < meta.length; i++) {
+        if (meta[i].getAttribute("name") == META_TAG_CABOTO) {
+            if (meta[i].getAttribute("content") !== null) {
+                annoUri = meta[i].getAttribute("content");
+            }
+        }
+    }
+
+    if (annoUri.length > 0) {
+        var req = new Ajax.Request('./annotation/about/', {
+            method:'get',
+            parameters: createParams(annoUri),
+            requestHeaders: {Accept: APPLICATION_JSON},
+            onSuccess: displayAnnotations,
+            onFailure: annotationFailure
+        });
+
+
+    } else {
+        document.getElementById("annotations-results").innerHTML = "<p>Something has gone wrong! " +
+                                                                   "The system is unable to determine the ID of the item that can be " +
+                                                                   "annotated.</p>";
+    }
+
+}
+
+function processForm(username) {
+
+    var uri = "./annotation/person/" + username + "/";
+
+    var message = "";
+
+    // http://rexchung.com/2007/02/22/getting-radio-buttons-value-with-prototypejs/
+    var privacy = Form.getInputs('annotation-comment-form', 'radio', 'privacy').find(function(radio)
+    {
+        return radio.checked;
+    }).value;
+
+    // default to public
+    if (privacy == null) {
+        privacy = "public";
+    }
+
+    uri += privacy + "/";
+
+    if (!Form.Element.present("annotation-title")) {
+        message += "You need to provide a title.";
+    }
+
+    if (!Form.Element.present("annotation-description1") && !Form.Element.present("annotation-description2")) {
+        message += "You need to provide at least one description.";
+    } else {
+
+    	for(i=1;i<3;i++) {
+    		var desc = Form.Element.getValue("annotation-description" + i);
+
+    		// <http://haacked.com/archive/2004/10/25/usingregularexpressionstomatchhtml.aspx>
+    		var matches = desc.match(/<\/?\w+((\s+\w+(\s*=\s*(?:\".*?\"|\'.*?\'|[^\'\">\s]+))?)+\s*|\s*)\/?>/);
+
+    		if (matches !== null) {
+    			message += "It looks like you have markup in your comment #"+i+" - not supported, sorry!";
+    		}
+        }
+    }
+
+
+    if (message.length > 0) {
+        document.getElementById("annotation-messages").innerHTML = "<p>" + message + "</p>";
+        return;
+    }
+
+    // serialize the form data
+    var s = Form.serialize("annotation-comment-form", true);
+
+    // we don't need to send the submit
+    delete s.submit;
+
+    // send the form details
+    var req = new Ajax.Request(uri, {
+        method:'post',
+        parameters: s,
+        onSuccess: findAnnotations,
+        onFailure: function(transport) {
+            alert(transport.responseText);
+        }
+    });
+
+}
+
+function initializeAnnotations() {
+
+    // hides the js support message..
+    displayMessage("<p>Loading annotations..</p>");
+
+    // enable the form
+    clearForm();
+
+    // get the annotations
+    findAnnotations();
+}
+
+function deleteAnnotation(uri) {
+
+    var req = new Ajax.Request(uri, {
+        method:'DELETE',
+        onSuccess: initializeAnnotations,
+        onFailure: function(n) {
+            alert("Failed!");
+        }
+    });
+}
+

File diff suppressed because it is too large
+ 4221 - 0
caboto-web-cas/src/main/webapp/js/prototype.js


+ 2 - 0
caboto-web-cas/src/main/webapp/logout.jsp

@@ -0,0 +1,2 @@
+<%request.getSession().invalidate();%>
+<%response.sendRedirect("./index.jsp");%>

+ 1 - 0
caboto-web-cas/src/main/webapp/secured/index.jsp

@@ -0,0 +1 @@
+<%response.sendRedirect("../index.jsp");%>

+ 72 - 0
caboto-web-cas/src/main/webapp/style.css

@@ -0,0 +1,72 @@
+body {
+    font-family: Arial, Helvetica, sans-serif;
+    font-size: 76%;
+    padding: 0;
+    margin: 0;
+}
+
+#container {
+    margin-left: 0.5em;
+    margin-right: 0.5em;
+}
+
+.logout {
+    text-align: center;
+}
+
+.annotation-entry {
+    padding-top: 0.4em;
+    padding-bottom: 1.5em;
+    clear: both;
+}
+
+#annotations-results {
+    border-bottom: darkgray dashed 1px;
+}
+
+.annotation-entry-title-public {
+    padding: 0.4em;
+    background-color: bisque;
+    font-weight: bold;
+    border-top: darkgray solid 1px;
+    border-bottom: darkgray solid 1px;
+}
+
+.annotation-entry-title-private {
+    padding: 0.4em;
+    background-color: tan;
+    font-weight: bold;
+    border-top: darkgray solid 1px;
+    border-bottom: darkgray solid 1px;
+}
+
+.annotation-entry-description {
+    margin-top: 0.4em;
+    margin-bottom: 0.4em;
+}
+
+.annotation-entry-created {
+    font-style: italic;
+    color: #008b8b;
+    float: left;
+}
+
+.fieldSet {
+    margin-top: 1em;
+}
+
+.fieldSet legend {
+    font-weight: bold;
+    font-size: 1.2em;
+}
+
+#annotation-messages {
+    font-style: italic;
+    font-weight: bold;
+    color: #ff0000;
+}
+
+.annotation-body {
+    font-family: Arial, Helvetica, sans-serif;
+    font-size: 1em;
+}

+ 48 - 0
caboto-web/pom.xml

@@ -0,0 +1,48 @@
+<?xml version="1.0"?>
+<project>
+    <parent>
+        <artifactId>caboto-parent</artifactId>
+        <groupId>org.caboto</groupId>
+        <version>0.10-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.caboto</groupId>
+    <artifactId>caboto-web</artifactId>
+    <packaging>war</packaging>
+    <name>caboto-web Maven Webapp</name>
+    <url>http://maven.apache.org</url>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.mortbay.jetty</groupId>
+                <artifactId>maven-jetty-plugin</artifactId>
+                <configuration>
+                    <connectors>
+                        <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
+                            <port>9090</port>
+                            <maxIdleTime>60000</maxIdleTime>
+                        </connector>
+                    </connectors>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <dependencies>
+        <dependency>
+            <groupId>org.caboto</groupId>
+            <artifactId>caboto</artifactId>
+            <version>${caboto.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-taglibs</artifactId>
+            <version>${spring.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>2.5</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>

+ 14 - 0
caboto-web/src/main/resources/profiles.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<profiles>
+    <profile id="SimpleComment" type="http://caboto.org/schema/annotations#SimpleComment">
+        <profileEntry id="title"
+                      propertyType="http://purl.org/dc/elements/1.1/title"
+                      objectDatatype="String"
+                      required="true" />
+        <profileEntry id="description"
+                      objectDatatype="String"
+                      propertyType="http://purl.org/dc/elements/1.1/description"
+                      required="true" />
+    </profile>
+
+</profiles>

+ 3 - 0
caboto-web/src/main/resources/realm.properties

@@ -0,0 +1,3 @@
+user1: secret123,USERS
+user2: secret123,USERS
+admin: admin123,USERS,ADMIN

+ 15 - 0
caboto-web/src/main/resources/sdb.ttl

@@ -0,0 +1,15 @@
+@prefix rdfs:   <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix rdf:    <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix ja:     <http://jena.hpl.hp.com/2005/11/Assembler#> .
+@prefix sdb:    <http://jena.hpl.hp.com/2007/sdb#> .
+
+_:c rdf:type sdb:SDBConnection ;
+    sdb:sdbType     "derby" ;
+    sdb:sdbName     "target/DB/SDB2" ;
+    sdb:driver      "org.apache.derby.jdbc.EmbeddedDriver" ;
+    .
+
+[] rdf:type sdb:Store ;
+   sdb:layout        "layout2" ;
+   sdb:connection     _:c ;
+   .

+ 5 - 0
caboto-web/src/main/resources/startup.properties

@@ -0,0 +1,5 @@
+# DO NOT CHANGE THIS VALUE (unless you know what you are doing)! :-p
+#
+# This property is used by the "StartUp" servlet to  determine if the underlying relational
+# database has been formatted for the RDF store.
+sdb.store.formatted=false

+ 112 - 0
caboto-web/src/main/webapp/WEB-INF/spring/caboto-context.xml

@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:security="http://www.springframework.org/schema/security"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans
+                           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+                           http://www.springframework.org/schema/security
+                           http://www.springframework.org/schema/security/spring-security-3.0.xsd
+                           http://www.springframework.org/schema/context
+                           http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+    <context:component-scan base-package="org.caboto"/>
+
+    <!-- repository gives access to the profile of allowed annotations -->
+    <bean id="profileRepository" class="org.caboto.profile.ProfileRepositoryXmlImpl">
+        <constructor-arg index="0" value="profiles.xml"/>
+    </bean>
+
+    <!-- factory creates SDB stores: provides access to the RDF store -->
+
+    <bean id="database" class="org.caboto.jena.db.impl.SDBDatabase">
+        <constructor-arg index="0" value="/sdb.ttl"/>
+    </bean>
+
+    <!-- DAO class for creating and retrieving annotations -->
+
+    <bean id="annotationDao" class="org.caboto.dao.AnnotationDaoImpl" scope="prototype">
+        <constructor-arg index="0" ref="profileRepository"/>
+        <constructor-arg index="1" ref="database"/>
+    </bean>
+
+    <!-- utility class for converting RDF annotations to JSON -->
+
+    <bean id="jsonSupport" class="org.caboto.CabotoJsonSupport"/>
+
+    <!-- SECURITY -->
+
+    <!-- gate keeper : authority on who can see what -->
+
+    <bean id="gatekeeper" class="org.caboto.security.spring.GateKeeperImpl">
+        <constructor-arg index="0" value="ROLE_ADMIN"/>
+    </bean>
+
+    <!-- aop implementation to filter results -->
+
+    <bean id="exitGuard" class="org.caboto.security.spring.ExitGuard">
+        <constructor-arg index="0" ref="gatekeeper"/>
+    </bean>
+
+    <bean id="annotationDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
+        <property name="proxyInterfaces" value="org.caboto.dao.AnnotationDao"/>
+        <property name="interceptorNames" value="exitGuard"/>
+        <property name="target" ref="annotationDao"/>
+    </bean>
+
+    <!-- HTTP restrictions -->
+
+    <security:http access-decision-manager-ref="decisionManager" path-type="regex"
+                   lowercase-comparisons="false">
+        <security:http-basic/>
+        <security:anonymous username="anonymous" granted-authority="ROLE_ANONYMOUS"/>
+        <security:intercept-url pattern="^.*/public/.*$" method="POST" access="IS_AUTHENTICATED_FULLY"/>
+        <security:intercept-url pattern="^.*/public/.*$" method="DELETE" access="IS_AUTHENTICATED_FULLY"/>
+        <security:intercept-url pattern="^.*/private/.*$" method="GET" access="IS_AUTHENTICATED_FULLY"/>
+        <security:intercept-url pattern="^.*/private/.*$" method="POST" access="IS_AUTHENTICATED_FULLY"/>
+        <security:intercept-url pattern="^.*/private/.*$" method="DELETE" access="IS_AUTHENTICATED_FULLY"/>
+        <security:intercept-url pattern="^.*/secured/.*$" method="GET" access="IS_AUTHENTICATED_FULLY"/>
+        <security:intercept-url pattern=".*" method="GET" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
+    </security:http>
+
+    <!-- default authentication provider -->
+
+    <security:authentication-manager>
+        <security:authentication-provider>
+            <security:user-service>
+                <security:user name="mike" password="potato" authorities="ROLE_USER"/>
+                <security:user name="damian" password="carrot" authorities="ROLE_USER"/>
+                <security:user name="jasper" password="turnip" authorities="ROLE_USER"/>
+                <security:user name="nikki" password="pea" authorities="ROLE_USER"/>
+                <security:user name="simon" password="radish" authorities="ROLE_USER"/>
+                <security:user name="andrew" password="onion" authorities="ROLE_USER"/>
+                <security:user name="admin" password="boss" authorities="ROLE_USER,ROLE_ADMIN"/>
+            </security:user-service>
+        </security:authentication-provider>
+    </security:authentication-manager>
+
+    <!-- voter for resources -->
+
+    <bean id="resourceVoter" class="org.caboto.security.spring.ResourceAccessDecisionVoter">
+        <constructor-arg index="0" ref="gatekeeper"/>
+        <constructor-arg index="1" value="annotation"/>
+    </bean>
+
+    <bean id="roleVoter" class="org.springframework.security.access.vote.RoleVoter"/>
+
+    <bean id="authenticatedVoter" class="org.springframework.security.access.vote.AuthenticatedVoter"/>
+
+    <!-- custom decision manager -->
+
+    <bean id="decisionManager" class="org.springframework.security.access.vote.UnanimousBased">
+        <property name="allowIfAllAbstainDecisions" value="true"/>
+        <property name="decisionVoters">
+            <list>
+                <ref local="roleVoter"/>
+                <ref local="authenticatedVoter"/>
+                <ref local="resourceVoter"/>
+            </list>
+        </property>
+    </bean>
+
+</beans>

+ 46 - 0
caboto-web/src/main/webapp/WEB-INF/web.xml

@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
+                             http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
+
+    <display-name>Caboto</display-name>
+
+
+    <filter>
+        <filter-name>springSecurityFilterChain</filter-name>
+        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
+    </filter>
+
+    <filter-mapping>
+        <filter-name>springSecurityFilterChain</filter-name>
+        <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
+
+    <context-param>
+        <param-name>contextConfigLocation</param-name>
+        <param-value>/WEB-INF/spring/caboto-context.xml</param-value>
+    </context-param>
+
+    <listener>
+        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
+    </listener>
+
+    <servlet>
+        <servlet-name>Jersey Spring Integration</servlet-name>
+        <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
+        <init-param>
+            <param-name>com.sun.jersey.config.property.packages</param-name>
+            <param-value>org.caboto.rest</param-value>
+        </init-param>
+        <load-on-startup>1</load-on-startup>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>Jersey Spring Integration</servlet-name>
+        <url-pattern>/annotation/*</url-pattern>
+    </servlet-mapping>
+
+
+</web-app>

+ 83 - 0
caboto-web/src/main/webapp/index.jsp

@@ -0,0 +1,83 @@
+<?xml version="1.0"?>
+<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:security="http://www.springframework.org/security/tags">
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <meta name="caboto-annotation" content="http://caboto.org"/>
+    <style type="text/css" media="screen">@import "./style.css";</style>
+    <script type="text/javascript" src="./js/prototype.js"></script>
+    <script type="text/javascript" src="./js/annotations.js"></script>
+    <title>Caboto Test Example</title>
+</head>
+<body onload="initializeAnnotations();">
+
+<security:authorize ifAnyGranted="ROLE_USER,ROLE_ADMIN">
+    <h3 class="logout"><a href="./logout.jsp">Logout</a></h3>
+</security:authorize>
+
+<%
+    Cookie uid = new Cookie("uid", null);
+    Cookie admin = new Cookie("admin", null);
+
+    if (request.getUserPrincipal() != null) {
+        uid.setValue(request.getUserPrincipal().getName());
+        if (request.isUserInRole("ADMIN")) {
+            admin.setValue("true");
+        }
+    }
+
+    response.addCookie(uid);
+    response.addCookie(admin);
+%>
+
+<div class="annotations">
+
+    <fieldset class="fieldSet">
+        <legend>Annotations</legend>
+        <div id="annotations-results"><p>Sorry, you need a JavaScript enabled browser.</p></div>
+
+
+        <p>Add your own annotation...</p>
+
+        <div id="annotation-messages"></div>
+
+        <%-- show form if they are logged in --%>
+        <security:authorize ifAnyGranted="ROLE_USER,ROLE_ADMIN">
+            <form id="annotation-comment-form"
+                  action="javascript:processForm('<%=request.getUserPrincipal().getName()%>')"
+                  method="post">
+                <p>
+                    <label><strong>Title:</strong></label><br/>
+                    <input id="annotation-title" type="text" name="title" size="50"/><br/>
+                    <label><strong>Description:</strong></label><br/>
+                    <textarea id="annotation-description1" rows="5" cols="50"
+                              name="description"></textarea><br/>
+                    <label><strong>Description (repeated field):</strong></label><br/>
+                    <textarea id="annotation-description2" rows="5" cols="50"
+                              name="description"></textarea><br/>
+                    <input type="radio" name="privacy" value="public" checked="checked"> Public
+                    <input type="radio" name="privacy" value="private"> Private
+                    <input type="hidden" name="type" value="SimpleComment"/>
+                    <input type="hidden" name="annotates" value="http://caboto.org"/><br />
+                    <input id="annotation-submit" type="submit" name="submit" value="Submit"
+                           disabled="disabled"/>
+                </p>
+            </form>
+        </security:authorize>
+
+        <%-- message if not logged in --%>
+        <security:authorize ifNotGranted="ROLE_USER,ROLE_ADMIN">
+            <p>You must be <a href="secured/">logged in</a> to add an annotation.</p>
+
+            <p>The test accounts are mike/potato, damian/carrot, jasper/turnip, nikki/pea,
+                simon/radish and admin/boss</p>
+
+        </security:authorize>
+
+    </fieldset>
+</div>
+
+</body>
+</html>

+ 287 - 0
caboto-web/src/main/webapp/js/annotations.js

@@ -0,0 +1,287 @@
+var META_TAG_CABOTO = "caboto-annotation";
+var APPLICATION_JSON = "application/json";
+
+function displayMessage(message) {
+    document.getElementById("annotations-results").innerHTML = message;
+}
+
+function createParams(uri) {
+
+    var params = "id=" + encodeURIComponent(uri);
+
+    // extra parameter to stop IE caching the ajax requests
+    if (Prototype.Browser.IE) {
+        params += "&ie-cache-hack=" + new Date().getTime();
+    }
+
+    return params;
+}
+
+function annotationFailure(fail) {
+
+    var error;
+
+    if (fail.status == 404) {
+        error = "<p>There are currently no annotations.</p>";
+    } else if (fail.status == 401) {
+        error = "<p>You are not authorized to view the annotations.</p>";
+    } else {
+        error = "<p>" + fail.status + " - something has gone wrong!";
+    }
+
+    displayMessage(error);
+}
+
+
+function getCookie(name) {
+
+    var cookie = null;
+
+    var allCookies = document.cookie;
+
+    var cookies = allCookies.split(";");
+
+    for (var i = 0; i < cookies.length; i++) {
+
+        var tmp = cookies[i].replace(/^\s+|\s+$/g, '').split("=");
+
+        if (tmp[0] == name) {
+            cookie = tmp[1];
+            break;
+        }
+    }
+
+    return cookie;
+}
+
+/*
+ Parses date strings that are in xsd:dateTime format.
+ */
+function parseDate(dateString) {
+
+    var d = new Date(dateString.substr(0, 19).replace(/-/g, '/').replace("T", " "));
+
+    d.setMinutes(d.getMinutes() + d.getTimezoneOffset());
+
+    var timeZone = dateString.substr(19).split(":");
+    timeZone = timeZone[0] * 60 + (timeZone[1] * 1);
+    d.setMinutes(d.getMinutes() + timeZone);
+
+    return d;
+}
+
+/*
+ Takes the URI that represents the person and returns just the username portion.
+ */
+function parseAuthor(author) {
+    var temp = author.split("/");
+    return temp[temp.length - 2];
+}
+
+function findType(aUri) {
+    var temp = aUri.split("/");
+    return temp[temp.length - 2];
+}
+
+function formatAnnotation(annotation, uid, admin) {
+
+	var date = parseDate(annotation.created);
+    var author = parseAuthor(annotation.author);
+//    var body = annotation.body.description.replace(/\n|\r/g, "<br />\n");
+    var type = findType(annotation.id);
+    
+    var body = "";
+    for(i=0; i<annotation.body.description.length; i++) {
+    	body += annotation.body.description[i] + "<br/>";
+    }
+
+    var output = "<div class='annotation-entry'>";
+
+    if (type == "private") {
+        output += "<div class='annotation-entry-title-private'>"
+    } else {
+        output += "<div class='annotation-entry-title-public'>"
+    }
+
+    output += annotation.body.title + "</div>" +
+              "<div class='annotation-entry-description'>" + body + "</div>" +
+              "<div><div class='annotation-entry-created'>" +
+              "Created on " + date.toLocaleString() + " by " + author + "</div>";
+
+    if (uid == author || admin == "true") {
+        output += "<div style='float: right'>[<a href='#' onclick='deleteAnnotation(\"" +
+                  annotation.id + "\")'>Delete</a>]</div>";
+    }
+
+    output += "</div>";
+
+    output += "</div>";
+
+    return output;
+}
+
+
+function clearForm() {
+
+    if (document.getElementById('annotation-comment-form')) {
+        Form.Element.enable('annotation-submit');
+        Form.Element.clear('annotation-title');
+        Form.Element.clear('annotation-description1');
+        Form.Element.clear('annotation-description2');
+    }
+    document.getElementById("annotation-messages").innerHTML = "";
+}
+
+/*
+ Display annotations - they are received in JSON format.
+ */
+function displayAnnotations(transport) {
+
+    var uid = getCookie("uid");
+    var admin = getCookie("admin");
+
+    var json = transport.responseText.evalJSON(true);
+
+    var output = "";
+
+    if (json.length > 0) {
+
+        json.sort(function(a, b) {
+            return parseDate(a.created) - parseDate(b.created);
+        });
+
+        for (var i = 0; i < json.length; i++) {
+            output += formatAnnotation(json[i], uid, admin);
+        }
+    } else {
+        output = "<p>There are no annotations.</p>";
+    }
+
+
+    displayMessage(output);
+    clearForm();
+}
+
+
+/*
+ Ajax call - find annotations that are about the "uri".
+ */
+function findAnnotations() {
+
+    var annoUri = "";
+
+    // find the URI of the thing we are interested in
+    var meta = document.getElementsByTagName("META");
+
+    for (var i = 0; i < meta.length; i++) {
+        if (meta[i].getAttribute("name") == META_TAG_CABOTO) {
+            if (meta[i].getAttribute("content") !== null) {
+                annoUri = meta[i].getAttribute("content");
+            }
+        }
+    }
+
+    if (annoUri.length > 0) {
+        var req = new Ajax.Request('./annotation/about/', {
+            method:'get',
+            parameters: createParams(annoUri),
+            requestHeaders: {Accept: APPLICATION_JSON},
+            onSuccess: displayAnnotations,
+            onFailure: annotationFailure
+        });
+
+
+    } else {
+        document.getElementById("annotations-results").innerHTML = "<p>Something has gone wrong! " +
+                                                                   "The system is unable to determine the ID of the item that can be " +
+                                                                   "annotated.</p>";
+    }
+
+}
+
+function processForm(username) {
+
+    var uri = "./annotation/person/" + username + "/";
+
+    var message = "";
+
+    // http://rexchung.com/2007/02/22/getting-radio-buttons-value-with-prototypejs/
+    var privacy = Form.getInputs('annotation-comment-form', 'radio', 'privacy').find(function(radio)
+    {
+        return radio.checked;
+    }).value;
+
+    // default to public
+    if (privacy == null) {
+        privacy = "public";
+    }
+
+    uri += privacy + "/";
+
+    if (!Form.Element.present("annotation-title")) {
+        message += "You need to provide a title.";
+    }
+
+    if (!Form.Element.present("annotation-description1") && !Form.Element.present("annotation-description2")) {
+        message += "You need to provide at least one description.";
+    } else {
+
+    	for(i=1;i<3;i++) {
+    		var desc = Form.Element.getValue("annotation-description" + i);
+
+    		// <http://haacked.com/archive/2004/10/25/usingregularexpressionstomatchhtml.aspx>
+    		var matches = desc.match(/<\/?\w+((\s+\w+(\s*=\s*(?:\".*?\"|\'.*?\'|[^\'\">\s]+))?)+\s*|\s*)\/?>/);
+
+    		if (matches !== null) {
+    			message += "It looks like you have markup in your comment #"+i+" - not supported, sorry!";
+    		}
+        }
+    }
+
+
+    if (message.length > 0) {
+        document.getElementById("annotation-messages").innerHTML = "<p>" + message + "</p>";
+        return;
+    }
+
+    // serialize the form data
+    var s = Form.serialize("annotation-comment-form", true);
+
+    // we don't need to send the submit
+    delete s.submit;
+
+    // send the form details
+    var req = new Ajax.Request(uri, {
+        method:'post',
+        parameters: s,
+        onSuccess: findAnnotations,
+        onFailure: function(transport) {
+            alert(transport.responseText);
+        }
+    });
+
+}
+
+function initializeAnnotations() {
+
+    // hides the js support message..
+    displayMessage("<p>Loading annotations..</p>");
+
+    // enable the form
+    clearForm();
+
+    // get the annotations
+    findAnnotations();
+}
+
+function deleteAnnotation(uri) {
+
+    var req = new Ajax.Request(uri, {
+        method:'DELETE',
+        onSuccess: initializeAnnotations,
+        onFailure: function(n) {
+            alert("Failed!");
+        }
+    });
+}
+

File diff suppressed because it is too large
+ 4221 - 0
caboto-web/src/main/webapp/js/prototype.js


+ 2 - 0
caboto-web/src/main/webapp/logout.jsp

@@ -0,0 +1,2 @@
+<%request.getSession().invalidate();%>
+<%response.sendRedirect("./index.jsp");%>

+ 1 - 0
caboto-web/src/main/webapp/secured/index.jsp

@@ -0,0 +1 @@
+<%response.sendRedirect("../index.jsp");%>

+ 72 - 0
caboto-web/src/main/webapp/style.css

@@ -0,0 +1,72 @@
+body {
+    font-family: Arial, Helvetica, sans-serif;
+    font-size: 76%;
+    padding: 0;
+    margin: 0;
+}
+
+#container {
+    margin-left: 0.5em;
+    margin-right: 0.5em;
+}
+
+.logout {
+    text-align: center;
+}
+
+.annotation-entry {
+    padding-top: 0.4em;
+    padding-bottom: 1.5em;
+    clear: both;
+}
+
+#annotations-results {
+    border-bottom: darkgray dashed 1px;
+}
+
+.annotation-entry-title-public {
+    padding: 0.4em;
+    background-color: bisque;
+    font-weight: bold;
+    border-top: darkgray solid 1px;
+    border-bottom: darkgray solid 1px;
+}
+
+.annotation-entry-title-private {
+    padding: 0.4em;
+    background-color: tan;
+    font-weight: bold;
+    border-top: darkgray solid 1px;
+    border-bottom: darkgray solid 1px;
+}
+
+.annotation-entry-description {
+    margin-top: 0.4em;
+    margin-bottom: 0.4em;
+}
+
+.annotation-entry-created {
+    font-style: italic;
+    color: #008b8b;
+    float: left;
+}
+
+.fieldSet {
+    margin-top: 1em;
+}
+
+.fieldSet legend {
+    font-weight: bold;
+    font-size: 1.2em;
+}
+
+#annotation-messages {
+    font-style: italic;
+    font-weight: bold;
+    color: #ff0000;
+}
+
+.annotation-body {
+    font-family: Arial, Helvetica, sans-serif;
+    font-size: 1em;
+}

+ 183 - 0
caboto/checkstyle.xml

@@ -0,0 +1,183 @@
+<?xml version="1.0"?>
+<!DOCTYPE module PUBLIC
+    "-//Puppy Crawl//DTD Check Configuration 1.2//EN"
+    "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
+
+<!--
+
+  Checkstyle configuration that checks the sun coding conventions from:
+
+    - the Java Language Specification at
+      http://java.sun.com/docs/books/jls/second_edition/html/index.html
+
+    - the Sun Code Conventions at http://java.sun.com/docs/codeconv/
+
+    - the Javadoc guidelines at
+      http://java.sun.com/j2se/javadoc/writingdoccomments/index.html
+
+    - the JDK Api documentation http://java.sun.com/j2se/docs/api/index.html
+
+    - some best practices
+
+  Checkstyle is very configurable. Be sure to read the documentation at
+  http://checkstyle.sf.net (or in your downloaded distribution).
+
+  Most Checks are configurable, be sure to consult the documentation.
+
+  To completely disable a check, just comment it out or delete it from the file.
+
+  Finally, it is worth reading the documentation.
+
+-->
+
+<module name="Checker">
+
+    <!-- Checks that a package.html file exists for each package.     -->
+    <!-- See http://checkstyle.sf.net/config_javadoc.html#PackageHtml -->
+    <module name="PackageHtml"/>
+
+    <!-- Checks whether files end with a new line.                        -->
+    <!-- See http://checkstyle.sf.net/config_misc.html#NewlineAtEndOfFile -->
+    <module name="NewlineAtEndOfFile"/>
+
+    <!-- Checks that property files contain the same keys.         -->
+    <!-- See http://checkstyle.sf.net/config_misc.html#Translation -->
+    <module name="Translation"/>
+
+
+    <module name="TreeWalker">
+
+        <!-- Checks for Javadoc comments.                     -->
+        <!-- See http://checkstyle.sf.net/config_javadoc.html -->
+        <module name="JavadocMethod"/>
+        <module name="JavadocType"/>
+        <module name="JavadocVariable"/>
+        <module name="JavadocStyle"/>
+
+
+        <!-- Checks for Naming Conventions.                  -->
+        <!-- See http://checkstyle.sf.net/config_naming.html -->
+        <module name="ConstantName"/>
+        <module name="LocalFinalVariableName"/>
+        <module name="LocalVariableName"/>
+        <module name="MemberName"/>
+        <module name="MethodName"/>
+        <module name="PackageName"/>
+        <module name="ParameterName"/>
+        <module name="StaticVariableName"/>
+        <module name="TypeName"/>
+
+
+        <!-- Checks for Headers                                -->
+        <!-- See http://checkstyle.sf.net/config_header.html   -->
+        <!-- <module name="Header">                            -->
+            <!-- The follow property value demonstrates the ability     -->
+            <!-- to have access to ANT properties. In this case it uses -->
+            <!-- the ${basedir} property to allow Checkstyle to be run  -->
+            <!-- from any directory within a project. See property      -->
+            <!-- expansion,                                             -->
+            <!-- http://checkstyle.sf.net/config.html#properties        -->
+            <!-- <property                                              -->
+            <!--     name="headerFile"                                  -->
+            <!--     value="${basedir}/java.header"/>                   -->
+        <!-- </module> -->
+
+        <!-- Following interprets the header file as regular expressions. -->
+        <!-- <module name="RegexpHeader"/>                                -->
+
+
+        <!-- Checks for imports                              -->
+        <!-- See http://checkstyle.sf.net/config_import.html -->
+        <module name="AvoidStarImport"/>
+        <module name="IllegalImport"/> <!-- defaults to sun.* packages -->
+        <module name="RedundantImport"/>
+        <module name="UnusedImports"/>
+
+
+        <!-- Checks for Size Violations.                    -->
+        <!-- See http://checkstyle.sf.net/config_sizes.html -->
+        <module name="FileLength"/>
+        <module name="LineLength">
+            <property name="max" value="100" />
+        </module>
+        <module name="MethodLength"/>
+        <module name="ParameterNumber"/>
+
+
+        <!-- Checks for whitespace                               -->
+        <!-- See http://checkstyle.sf.net/config_whitespace.html -->
+        <module name="EmptyForIteratorPad"/>
+        <module name="MethodParamPad"/>
+        <module name="NoWhitespaceAfter"/>
+        <module name="NoWhitespaceBefore"/>
+        <module name="OperatorWrap"/>
+        <module name="ParenPad"/>
+        <module name="TypecastParenPad"/>
+        <module name="TabCharacter"/>
+        <module name="WhitespaceAfter"/>
+        <module name="WhitespaceAround">
+            <property name="tokens" value="ASSIGN,BAND,BAND_ASSIGN,BOR,BOR_ASSIGN,BSR,BSR_ASSIGN,
+            BXOR,BXOR_ASSIGN,COLON,DIV,DIV_ASSIGN,EQUAL,GE,GT,LAND,LCURLY,LE,LITERAL_ASSERT,
+            LITERAL_CATCH,LITERAL_DO,LITERAL_ELSE,LITERAL_FINALLY,LITERAL_FOR,LITERAL_IF,
+            LITERAL_RETURN,LITERAL_SYNCHRONIZED,LITERAL_TRY,LITERAL_WHILE,LOR,LT,MINUS,MINUS_ASSIGN,
+            MOD,MOD_ASSIGN,NOT_EQUAL,PLUS,PLUS_ASSIGN,QUESTION,RCURLY,SL,SLIST,SL_ASSIGN,SR,
+            SR_ASSIGN,STAR,STAR_ASSIGN,LITERAL_ASSERT,TYPE_EXTENSION_AND,WILDCARD_TYPE"/>
+        </module>
+
+
+        <!-- Modifier Checks                                    -->
+        <!-- See http://checkstyle.sf.net/config_modifiers.html -->
+        <module name="ModifierOrder"/>
+        <module name="RedundantModifier"/>
+
+
+        <!-- Checks for blocks. You know, those {}'s         -->
+        <!-- See http://checkstyle.sf.net/config_blocks.html -->
+        <module name="AvoidNestedBlocks"/>
+        <module name="EmptyBlock"/>
+        <module name="LeftCurly"/>
+        <module name="NeedBraces"/>
+        <module name="RightCurly"/>
+
+
+        <!-- Checks for common coding problems               -->
+        <!-- See http://checkstyle.sf.net/config_coding.html -->
+        <module name="AvoidInlineConditionals"/>
+        <module name="DoubleCheckedLocking"/>    <!-- MY FAVOURITE -->
+        <module name="EmptyStatement"/>
+        <module name="EqualsHashCode"/>
+        <module name="HiddenField">
+            <property name="ignoreConstructorParameter" value="true"/>
+            <property name="ignoreSetter" value="true"/>
+        </module>
+        <module name="IllegalInstantiation"/>
+        <module name="InnerAssignment"/>
+        <module name="MagicNumber"/>
+        <module name="MissingSwitchDefault"/>
+        <module name="RedundantThrows"/>
+        <module name="SimplifyBooleanExpression"/>
+        <module name="SimplifyBooleanReturn"/>
+
+        <!-- Checks for class design                         -->
+        <!-- See http://checkstyle.sf.net/config_design.html -->
+        <module name="DesignForExtension"/>
+        <module name="FinalClass"/>
+        <module name="HideUtilityClassConstructor"/>
+        <module name="InterfaceIsType"/>
+        <module name="VisibilityModifier"/>
+
+
+        <!-- Miscellaneous other checks.                   -->
+        <!-- See http://checkstyle.sf.net/config_misc.html -->
+        <module name="ArrayTypeStyle"/>
+        <module name="FinalParameters"/>
+        <module name="GenericIllegalRegexp">
+            <property name="format" value="\s+$"/>
+            <property name="message" value="Line has trailing spaces."/>
+        </module>
+        <module name="TodoComment"/>
+        <module name="UpperEll"/>
+
+    </module>
+
+</module>

+ 231 - 0
caboto/pom.xml

@@ -0,0 +1,231 @@
+<?xml version="1.0"?>
+<project>
+    <parent>
+        <artifactId>caboto-parent</artifactId>
+        <groupId>org.caboto</groupId>
+        <version>0.10-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>org.caboto</groupId>
+    <artifactId>caboto</artifactId>
+    <name>caboto</name>
+    <url>http://maven.apache.org</url>
+    <build>
+        <filters>
+            <filter>src/main/filters/filter.properties</filter>
+        </filters>
+        <resources>
+            <resource>
+                <directory>src/main/resources</directory>
+                <filtering>true</filtering>
+            </resource>
+        </resources>
+
+        <plugins>
+
+            <!-- jetty plugin -->
+            <plugin>
+                <groupId>org.mortbay.jetty</groupId>
+                <artifactId>maven-jetty-plugin</artifactId>
+                <version>6.1.10</version>
+                <configuration>
+                    <connectors>
+                        <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector">
+                            <port>9090</port>
+                            <maxIdleTime>60000</maxIdleTime>
+                        </connector>
+                    </connectors>
+                </configuration>
+            </plugin>
+
+            <!-- clean up plugin -->
+            <plugin>
+                <inherited>false</inherited>
+                <artifactId>maven-clean-plugin</artifactId>
+                <configuration>
+                    <filesets>
+                        <fileset>
+                            <directory>.</directory>
+                            <includes>
+                                <include>derby.log</include>
+                            </includes>
+                        </fileset>
+                    </filesets>
+                </configuration>
+            </plugin>
+
+        </plugins>
+
+    </build>
+
+    <repositories>
+        <!-- java.net repo -->
+        <repository>
+            <id>maven2-repository.dev.java.net</id>
+            <name>Java.net Repository for Maven</name>
+            <url>http://download.java.net/maven/2/</url>
+            <layout>default</layout>
+        </repository>
+        <repository>
+            <id>maven-repository.dev.java.net</id>
+            <name>Java.net Maven 1 Repository (legacy)</name>
+            <url>http://download.java.net/maven/1</url>
+            <layout>legacy</layout>
+        </repository>
+    </repositories>
+    <reporting>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-checkstyle-plugin</artifactId>
+                <configuration>
+                    <configLocation>checkstyle.xml</configLocation>
+                </configuration>
+            </plugin>
+        </plugins>
+    </reporting>
+    <dependencies>
+
+        <dependency>
+            <groupId>org.caboto</groupId>
+            <artifactId>database</artifactId>
+            <version>${caboto.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>${junit.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.derby</groupId>
+            <artifactId>derby</artifactId>
+            <version>10.3.2.1</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.mortbay.jetty</groupId>
+            <artifactId>jetty</artifactId>
+            <version>6.1.3</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.hp.hpl.jena</groupId>
+            <artifactId>sdb</artifactId>
+            <version>${sdb.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+
+        <!-- jersey dependencies -->
+
+        <dependency>
+            <groupId>com.sun.jersey</groupId>
+            <artifactId>jersey-server</artifactId>
+            <version>${jersey.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.sun.jersey</groupId>
+            <artifactId>jersey-json</artifactId>
+            <version>${jersey.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.sun.jersey</groupId>
+            <artifactId>jersey-client</artifactId>
+            <version>${jersey.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <!-- spring dependencies -->
+
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>2.5</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-web</artifactId>
+            <version>${spring.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-aop</artifactId>
+            <version>${spring.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-jdbc</artifactId>
+            <version>${spring.version}</version>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-core</artifactId>
+            <version>${spring.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-web</artifactId>
+            <version>${spring.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-config</artifactId>
+            <version>${spring.version}</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.sun.jersey.contribs</groupId>
+            <artifactId>jersey-spring</artifactId>
+            <version>${jersey.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-core</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-webmvc</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-beans</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-context</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.springframework</groupId>
+                    <artifactId>spring-web</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>javax.servlet</groupId>
+                    <artifactId>servlet-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+
+    </dependencies>
+
+</project>

+ 1 - 0
caboto/src/main/filters/filter.properties

@@ -0,0 +1 @@
+caboto.version=${project.version}

+ 138 - 0
caboto/src/main/java/org/caboto/CabotoJsonSupport.java

@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2008, University of Bristol
+ * Copyright (c) 2008, University of Manchester
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1) Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ * 2) Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3) Neither the names of the University of Bristol and the
+ *    University of Manchester nor the names of their
+ *    contributors may be used to endorse or promote products derived from this
+ *    software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+package org.caboto;
+
+import org.codehaus.jettison.json.JSONArray;
+import org.codehaus.jettison.json.JSONException;
+import org.codehaus.jettison.json.JSONObject;
+
+import com.hp.hpl.jena.rdf.model.Literal;
+import com.hp.hpl.jena.rdf.model.Model;
+import com.hp.hpl.jena.rdf.model.ResIterator;
+import com.hp.hpl.jena.rdf.model.Resource;
+import com.hp.hpl.jena.rdf.model.Statement;
+import com.hp.hpl.jena.rdf.model.StmtIterator;
+
+/**
+ * A utility class used to convert RDF (Jena Model and Resource classes) to JSON.
+ *
+ * @author Mike Jones (mike.a.jones@bristol.ac.uk)
+ * @version $Id: CabotoJsonSupport.java 177 2008-05-30 13:50:59Z mike.a.jones $
+ */
+public class CabotoJsonSupport {
+
+    public CabotoJsonSupport() {
+    }
+
+    public JSONArray generateJsonArray(Model model) throws JSONException {
+
+        JSONArray jsonArray = new JSONArray();
+
+        ResIterator iter = model.listSubjects();
+
+        while (iter.hasNext()) {
+
+            Resource r = (Resource) iter.next();
+
+            if (!r.getURI().endsWith("#body")) {
+                jsonArray.put(generateJsonObject(r));
+            }
+
+        }
+
+        return jsonArray;
+    }
+
+    public JSONObject generateJsonObject(Resource resource) throws JSONException {
+    	return generateJsonObject(resource, false);
+    }
+    
+    public JSONObject generateJsonObject(Resource resource, boolean multiValued) throws JSONException {
+
+        JSONObject jsonObject = new JSONObject();
+        jsonObject.put("id", resource.getURI());
+
+        // get the statements that make up the resource
+        StmtIterator stmtIter = resource.listProperties();
+
+        while (stmtIter.hasNext()) {
+
+            // get the statment
+            Statement stmt = (Statement) stmtIter.next();
+
+            String key = getlocalName(stmt.getPredicate());
+
+            // get the values ...
+            if (stmt.getObject().isResource()) {
+
+                Resource r = (Resource) stmt.getObject();
+
+                if (!r.listProperties().hasNext()) {
+
+                    if (key.equals("type")) {
+                        jsonObject.put(key, getlocalName(r));
+                    } else {
+                        jsonObject.put(key, r.getURI());
+                    }
+
+                } else {
+                    jsonObject.put(key, generateJsonObject((Resource) stmt.getObject(), true));
+                }
+
+            } else if (stmt.getObject().isLiteral()) {
+            	if(multiValued) {
+            		if(!jsonObject.has(key)) {
+            			jsonObject.put(key, new JSONArray());
+            		}
+            		jsonObject.getJSONArray(key).put(((Literal) stmt.getObject()).getLexicalForm());
+            	} else {
+            		jsonObject.put(key, ((Literal) stmt.getObject()).getLexicalForm());
+            	}
+            }
+
+        }
+        return jsonObject;
+    }
+
+    private String getlocalName(Resource resource) {
+
+        // use the local name as the key if possible
+        if (resource.getLocalName() != null) {
+            return resource.getLocalName();
+        } else {
+            return resource.getURI();
+        }
+    }
+
+}

+ 182 - 0
caboto/src/main/java/org/caboto/CabotoUtility.java

@@ -0,0 +1,182 @@
+/*
+ * Copyright (c) 2008, University of Bristol
+ * Copyright (c) 2008, University of Manchester
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1) Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ * 2) Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * 3) Neither the names of the University of Bristol and the
+ *    University of Manchester nor the names of their
+ *    contributors may be used to endorse or promote products derived from this
+ *    software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+package org.caboto;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.UUID;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+/**
+ * <p>A generic utility class with static methods that are used across a number of classes.</p>
+ *
+ * @author Mike Jones (mike.a.jones@bristol.ac.uk)
+ * @version $Id: CabotoUtility.java 177 2008-05-30 13:50:59Z mike.a.jones $
+ */
+public final class CabotoUtility {
+
+	private static String DATE_FORMAT_STRING="yyyy-MM-dd'T'HH:mm:ssZ";
+	
+    /**
+     * Private constructor.
+     */
+    private CabotoUtility() {
+    }
+
+    /**
+     * <p>Generates a unique URI based on a UUID. The base of the URI needs to be provided.</p>
+     *
+     * @param graphId the base of the URI.
+     * @return a unique URI based on a UUID.
+     */
+    public static String generateId(final String graphId) {
+
+        String id = UUID.randomUUID().toString();
+
+        if (!graphId.endsWith("/")) {
+            id = "/" + id;
+        }
+
+        return graphId + id;
+    }
+    
+    
+    public static String getGraphId(final String id) {
+
+    	int index=id.lastIndexOf("/");
+    	if (index!=-1) {
+    		return id.substring(0,index+1);
+    		
+    	}
+        return null;
+    }
+    
+    public static String getId(final String uri) {
+
+    	int index=uri.lastIndexOf("/");
+    	if (index!=-1) {
+    		return uri.substring(index+1);
+    	}
+        return null;
+    }
+
+    /**
+     * <p>Parses a date to a format that is a valid XSD:dateTime.</p>
+     *
+     * @param date the date object to be parsed.
+     * @return the date represented as a valid XSD:dateTime.
+     */
+    public static String parseDate(final Date date) {
+
+        String temp = new SimpleDateFormat(DATE_FORMAT_STRING).format(date);
+
+        return temp.substring(0, temp.length() - 2) + ":"
+                + temp.substring(temp.length() - 2, temp.length());
+    }
+
+    public static Date parseDate (final String XsdDate) throws ParseException{
+    	String temp = XsdDate.substring(0, XsdDate.length() - 3)
+        + XsdDate.substring(XsdDate.length() - 2, XsdDate.length());
+
+    	Date date = new SimpleDateFormat(DATE_FORMAT_STRING).parse(temp);
+    	return date;
+    }
+    
+    
+    /**
+     * @param path the path details of a request.
+     * @return whether or not its a public resource.
+     */
+    public static boolean isPublicResource(String path) {
+        return publicResourcePattern.matcher(path).find();
+    }
+
+    /**
+     * @param path the path details of a request.
+     * @return whether or not its a private resource.
+     */
+    public static boolean isPrivateResource(String path) {
+        return privateResourcePattern.matcher(path).find();
+    }
+
+    /**
+     * @param graphUri the graph of an annotation.
+     * @return whether or not its a public graph.
+     */
+    public static boolean isPublicGraph(String graphUri) {
+        return publicGraphPattern.matcher(graphUri).find();
+    }
+
+    /**
+     * @param graphUri the graph of an annotation.
+     * @return whether or not its a private graph.
+     */
+    public static boolean isPrivateGraph(String graphUri) {
+        return privateGraphPattern.matcher(graphUri).find();
+    }
+
+
+    /**
+     * @param path the path details of a request.
+     * @return the username that is part of the path.
+     */
+    public static String extractUsername(String path) {
+	Matcher m = usernamePattern.matcher(path);
+	if(m.matches()) {
+            try {
+                return URLDecoder.decode(m.group(1), "UTF-8");
+            } catch (UnsupportedEncodingException ex) {
+                return m.group(1);
+            }
+	}
+	// can't recover from this!
+	throw new RuntimeException("No username found in the path.");
+    }
+
+    private static Pattern usernamePattern = Pattern.compile("^.*/person/(.*)/(public|private)/[^/]*$");
+
+    private static Pattern publicResourcePattern = Pattern.compile("^.*/person/.*/public/.*$");
+    private static Pattern privateResourcePattern = Pattern.compile("^.*/person/.*/private/.*$");
+
+    private static Pattern publicGraphPattern = Pattern.compile("^.*/person/[^/]*/public/$");
+    private static Pattern privateGraphPattern = Pattern.compile("^.*/person/[^/]*/private/$");
+
+
+    private static String personPath = "/person/";
+
+}

+ 551 - 0
caboto/src/main/java/org/caboto/OpAsQuery.java

@@ -0,0 +1,551 @@
+/*
+ * (c) Copyright 2007, 2008, 2009 Hewlett-Packard Development Company, LP
+ * All rights reserved.
+ * [See end of file]
+ */
+
+package org.caboto;
+
+import java.util.Collections ;
+import java.util.Iterator ;
+import java.util.List ;
+import java.util.HashMap ;
+import java.util.Map ;
+import java.util.Stack ;
+
+import com.hp.hpl.jena.sparql.core.VarExprList ;
+import com.hp.hpl.jena.sparql.expr.ExprAggregator ;
+import com.hp.hpl.jena.graph.Node ;
+import com.hp.hpl.jena.graph.Triple ;
+import com.hp.hpl.jena.query.Query ;
+import com.hp.hpl.jena.query.QueryFactory ;
+import com.hp.hpl.jena.query.SortCondition ;
+import com.hp.hpl.jena.query.Syntax ;
+import com.hp.hpl.jena.sparql.ARQInternalErrorException ;
+import com.hp.hpl.jena.sparql.ARQNotImplemented ;
+import com.hp.hpl.jena.sparql.algebra.Op;
+import com.hp.hpl.jena.sparql.algebra.OpVisitor;
+import com.hp.hpl.jena.sparql.algebra.op.* ;
+import com.hp.hpl.jena.sparql.core.BasicPattern ;
+import com.hp.hpl.jena.sparql.core.Var ;
+import com.hp.hpl.jena.sparql.expr.Expr ;
+import com.hp.hpl.jena.sparql.expr.ExprList ;
+import com.hp.hpl.jena.sparql.pfunction.PropFuncArg ;
+import com.hp.hpl.jena.sparql.syntax.* ;
+import com.hp.hpl.jena.sparql.util.graph.GraphList ;
+import com.hp.hpl.jena.vocabulary.RDF ;
+
+/** Convert an Op expression in SPARQL syntax, that is, the reverse of algebra generation */   
+public class OpAsQuery
+{
+    public static Query asQuery(Op op)
+    {
+        Query query = QueryFactory.make() ;
+        
+        Converter v = new Converter(query) ;
+        //OpWalker.walk(op, v) ;
+        op.visit(v) ;
+        
+        List<Var> vars = v.projectVars;
+        query.setQueryResultStar(vars.isEmpty()); // SELECT * unless we are projecting
+        Iterator<Var> iter = vars.iterator();
+        for (; iter.hasNext();) {
+            Var var = iter.next();
+            if (v.varExpression.containsKey(var))
+                query.addResultVar(var, v.varExpression.get(var));
+            else
+                query.addResultVar(var);
+        }
+        
+        ElementGroup eg = v.currentGroup ;
+        query.setQueryPattern(eg) ;
+        query.setQuerySelectType() ;
+        
+        query.setResultVars() ; 
+        return query ; 
+    }
+    
+    public static class Converter implements OpVisitor
+    {
+        private Query query ;
+        private Element element = null ;
+        private ElementGroup currentGroup = null ;
+        private Stack<ElementGroup> stack = new Stack<ElementGroup>() ;
+        private List<Var> projectVars = Collections.EMPTY_LIST ;
+        private Map<Var, Expr> varExpression = new HashMap<Var, Expr>() ;
+        
+        public Converter(Query query)
+        {
+            this.query = query ;
+            currentGroup = new ElementGroup() ;
+        }
+
+        Element asElement(Op op)
+        {
+            ElementGroup g = asElementGroup(op) ;
+            if ( g.getElements().size() == 1 )
+                return g.getElements().get(0) ;
+            return g ;
+        }
+        
+        ElementGroup asElementGroup(Op op)
+        {
+            startSubGroup() ;
+            op.visit(this) ;
+            return endSubGroup() ;
+        }
+
+        public void visit(OpBGP opBGP)
+        {
+            currentGroup().addElement(process(opBGP.getPattern())) ;
+        }
+
+//        public void visit(OpPropFunc opPropFunc)
+//        {
+//            OpBGP opBGP = opPropFunc.getBGP() ;
+//            currentGroup().addElement(process(opBGP.getPattern())) ;
+//        }
+        
+        public void visit(OpTriple opTriple)
+        { currentGroup().addElement(process(opTriple.getTriple())) ; }
+
+        public void visit(OpProcedure opProcedure)
+        {
+            throw new ARQNotImplemented("OpProcedure") ;
+        }
+        
+        public void visit(OpPropFunc opPropFunc)
+        {
+            Node s = processPropFuncArg(opPropFunc.getSubjectArgs()) ;
+            Node o = processPropFuncArg(opPropFunc.getObjectArgs()) ;
+            Triple t = new Triple(s, opPropFunc.getProperty(), o) ;
+            currentGroup().addElement(process(t)) ;
+        }
+        
+        private Node processPropFuncArg(PropFuncArg args)
+        {
+            if ( args.isNode() )
+                return args.getArg() ;
+
+            // List ...
+            List<Node> list = args.getArgList() ;
+            if ( list.size() == 0 )
+                return RDF.Nodes.nil ;
+            BasicPattern bgp = new BasicPattern() ;
+            Node head = GraphList.listToTriples(list, bgp) ;
+            currentGroup().addElement(process(bgp)) ;
+            return head ;
+        }
+        
+        public void visit(OpSequence opSequence)
+        {
+            ElementGroup g = currentGroup() ;
+            boolean nestGroup = ! g.isEmpty() ;
+            if ( nestGroup )
+            {
+                startSubGroup() ;
+                g = currentGroup() ;
+            }
+            
+            Iterator<Op> iter = opSequence.iterator() ;
+            
+            for ( ; iter.hasNext() ; )
+            {
+                Op op = iter.next() ;
+                Element e = asElement(op) ;
+                g.addElement(e) ;
+            }
+            if ( nestGroup )
+                endSubGroup() ;
+            return ;
+        }
+        
+        public void visit(OpDisjunction opDisjunction)
+        {
+            throw new ARQNotImplemented("OpDisjunction") ;
+        }
+
+        private Element process(BasicPattern pattern)
+        {
+            // The different SPARQL versions use different internal structures for BGPs.
+            if ( query.getSyntax() == Syntax.syntaxSPARQL_10 )
+            {
+                ElementTriplesBlock e = new ElementTriplesBlock() ;
+                for (Triple t : pattern)
+                    // Leave bNode variables as they are
+                    // Query serialization will deal with them. 
+                    e.addTriple(t) ;
+                return e ;
+            }
+            
+            if ( query.getSyntax() == Syntax.syntaxSPARQL_11 ||
+                 query.getSyntax() == Syntax.syntaxARQ )
+            {
+                ElementPathBlock e = new ElementPathBlock() ;
+                for (Triple t : pattern)
+                    // Leave bNode variables as they are
+                    // Query serialization will deal with them. 
+                    e.addTriple(t) ;
+                return e ;
+            }
+            
+            throw new ARQInternalErrorException("Unrecognized syntax: "+query.getSyntax()) ;
+            
+        }
+        
+        private ElementTriplesBlock process(Triple triple)
+        {
+            // Unsubtle
+            ElementTriplesBlock e = new ElementTriplesBlock() ;
+            e.addTriple(triple) ;
+            return e ;
+        }
+        
+        public void visit(OpQuadPattern quadPattern)
+        { throw new ARQNotImplemented("OpQuadPattern") ; }
+
+        public void visit(OpPath opPath)
+        { throw new ARQNotImplemented("OpPath") ; }
+
+        public void visit(OpJoin opJoin)
+        {
+            // Keep things clearly separated.
+            Element eLeft = asElement(opJoin.getLeft()) ;
+            Element eRight = asElementGroup(opJoin.getRight()) ;
+            
+            ElementGroup g = currentGroup() ;
+            g.addElement(eLeft) ;
+            g.addElement(eRight) ;
+            return ;
+        }
+
+        private static boolean emptyGroup(Element element)
+        {
+            if ( ! ( element instanceof ElementGroup ) )
+                return false ;
+            ElementGroup eg = (ElementGroup)element ;
+            return eg.isEmpty() ;
+        }
+        
+        public void visit(OpLeftJoin opLeftJoin)
+        {
+            Element eLeft = asElement(opLeftJoin.getLeft()) ;
+            ElementGroup eRight = asElementGroup(opLeftJoin.getRight()) ;
+            
+            if ( opLeftJoin.getExprs() != null )
+            {
+                for ( Expr expr : opLeftJoin.getExprs() )
+                {
+                    ElementFilter f = new ElementFilter(expr) ;
+                    eRight.addElement(f) ;
+                }
+            }
+            ElementGroup g = currentGroup() ;
+            if ( ! emptyGroup(eLeft) )
+                g.addElement(eLeft) ;
+            ElementOptional opt = new ElementOptional(eRight) ;
+            g.addElement(opt) ;
+        }
+
+        public void visit(OpDiff opDiff)
+        { throw new ARQNotImplemented("OpDiff") ; }
+
+        public void visit(OpMinus opMinus)
+        { throw new ARQNotImplemented("OpMinus") ; }
+
+        public void visit(OpUnion opUnion)
+        {
+            Element eLeft = asElementGroup(opUnion.getLeft()) ;
+            Element eRight = asElementGroup(opUnion.getRight()) ;
+            if ( eLeft instanceof ElementUnion )
+            {
+                ElementUnion elUnion = (ElementUnion)eLeft ;
+                elUnion.addElement(eRight) ;
+                return ;
+            }
+            
+//            if ( eRight instanceof ElementUnion )
+//            {
+//                ElementUnion elUnion = (ElementUnion)eRight ;
+//                elUnion.getElements().add(0, eLeft) ;
+//                return ;
+//            }
+            
+            ElementUnion elUnion = new ElementUnion() ;
+            elUnion.addElement(eLeft) ;
+            elUnion.addElement(eRight) ;
+            currentGroup().addElement(elUnion) ;
+        }
+
+        public void visit(OpConditional opCondition)
+        { throw new ARQNotImplemented("OpCondition") ; }
+
+        public void visit(OpFilter opFilter)
+        {
+            // (filter .. (filter ( ... ))   (non-canonicalizing OpFilters)
+            // Inner gets Grouped unnecessarily. 
+            Element e = asElement(opFilter.getSubOp()) ;
+            if ( currentGroup() != e )
+                currentGroup().addElement(e) ;
+            element = currentGroup() ;      // Was cleared by asElement. 
+            
+            ExprList exprs = opFilter.getExprs() ;
+            for ( Expr expr : exprs )
+            {
+                ElementFilter f = new ElementFilter(expr) ;
+                currentGroup().addElement(f) ;
+            }
+        }
+
+        public void visit(OpGraph opGraph)
+        {
+            startSubGroup() ;
+            Element e = asElement(opGraph.getSubOp()) ;
+            ElementGroup g = endSubGroup() ;
+            
+            Element graphElt = new ElementNamedGraph(opGraph.getNode(), e) ;
+            currentGroup().addElement(graphElt) ;
+        }
+
+        public void visit(OpService opService)
+        { 
+            // Hmm - if the subnode has been optimized, we may fail.
+            Op op = opService.getSubOp() ;
+            Element x = asElement(opService.getSubOp()) ; 
+            Element elt = new ElementService(opService.getService(), x) ;
+            currentGroup().addElement(elt) ;
+        }
+        
+        public void visit(OpDatasetNames dsNames)
+        { throw new ARQNotImplemented("OpDatasetNames") ; }
+
+        public void visit(OpTable opTable)
+        { 
+            // This will go in a group so simply forget it. 
+            if ( opTable.isJoinIdentity() ) return ;
+            throw new ARQNotImplemented("OpTable") ;
+        }
+
+        public void visit(OpExt opExt)
+        {
+//            Op op = opExt.effectiveOp() ;
+//            // This does not work in all cases.
+//            op.visit(this) ;
+            throw new ARQNotImplemented("OpExt") ;
+        }
+
+        public void visit(OpNull opNull)
+        { /*throw new ARQNotImplemented("OpNull") ;*/ }
+
+        public void visit(OpLabel opLabel)
+        {
+            if ( opLabel.hasSubOp() )
+                opLabel.getSubOp().visit(this) ;
+        }
+
+        public void visit(OpAssign opAssign)
+        {
+	        /**
+             * Special case: group involves and internal assignment
+             * e.g.  (assign ((?.1 ?.0)) (group () ((?.0 (count)))
+             * We attempt to intercept that here.
+             */
+            if (opAssign.getSubOp() instanceof OpGroup) {
+                Map<Var, Var> subs = new HashMap<Var, Var>();
+                Expr exp;
+                for (Var v: opAssign.getVarExprList().getVars()) {
+                    exp = opAssign.getVarExprList().getExpr(v);
+                    if (exp.isVariable()) subs.put(exp.asVar(), v);
+                    else throw new ARQNotImplemented("Expected simple assignment for group");
+                }
+                visit((OpGroup) opAssign.getSubOp(), subs);
+                return;
+            }
+
+            opAssign.getSubOp().visit(this) ;
+            for ( Var v : opAssign.getVarExprList().getVars() )
+            {
+                Element elt = new ElementAssign(v, opAssign.getVarExprList().getExpr(v)) ;
+                ElementGroup g = currentGroup() ;
+                g.addElement(elt) ;
+            }
+        }
+
+        public void visit(OpExtend opExtend)
+        { 
+            /**
+             * Special case: group involves and internal assignment
+             * e.g.  (assign ((?.1 ?.0)) (group () ((?.0 (count)))
+             * We attempt to intercept that here.
+             */
+            if (opExtend.getSubOp() instanceof OpGroup) {
+                Map<Var, Var> subs = new HashMap<Var, Var>();
+                Expr exp;
+                for (Var v: opExtend.getVarExprList().getVars()) {
+                    exp = opExtend.getVarExprList().getExpr(v);
+                    if (exp.isVariable()) subs.put(exp.asVar(), v);
+                    else throw new ARQNotImplemented("Expected simple assignment for group");
+                }
+                visit((OpGroup) opExtend.getSubOp(), subs);
+                return;
+            }
+
+            opExtend.getSubOp().visit(this) ;
+            for ( Var v : opExtend.getVarExprList().getVars() )
+            {
+                Element elt = new ElementAssign(v, opExtend.getVarExprList().getExpr(v)) ;
+                ElementGroup g = currentGroup() ;
+                g.addElement(elt) ;
+            }
+        }
+
+        
+        public void visit(OpList opList)
+        { /* No action */ }
+
+        public void visit(OpOrder opOrder)
+        {
+            List<SortCondition> x = opOrder.getConditions() ;
+            for ( SortCondition sc : x )
+                query.addOrderBy(sc);
+            opOrder.getSubOp().visit(this) ;
+        }
+
+        public void visit(OpProject opProject)
+        {
+            // Defer adding result vars until the end.
+            // OpGroup generates dupes otherwise
+            this.projectVars = opProject.getVars();
+            opProject.getSubOp().visit(this) ;
+        }
+
+        public void visit(OpReduced opReduced)
+        { 
+            query.setReduced(true) ;
+            opReduced.getSubOp().visit(this) ;
+        }
+
+        public void visit(OpDistinct opDistinct)
+        { 
+            query.setDistinct(true) ;
+            opDistinct.getSubOp().visit(this) ;
+        }
+
+        public void visit(OpSlice opSlice)
+        {
+            if ( opSlice.getStart() != Query.NOLIMIT )
+                query.setOffset(opSlice.getStart()) ;
+            if ( opSlice.getLength() != Query.NOLIMIT )
+                query.setLimit(opSlice.getLength()) ;
+            opSlice.getSubOp().visit(this) ;
+        }
+		
+        public void visit(OpGroup opGroup) {
+            visit(opGroup, Collections.EMPTY_MAP);
+        }
+        
+        // Specialised to cope with the preceeding assigns
+        public void visit(OpGroup opGroup, Map<Var, Var> subs) {            
+            List<ExprAggregator> a = opGroup.getAggregators();
+            
+            for (ExprAggregator ea : a) {
+                // Substitute generated var for actual
+                Var givenVar = ea.getAggVar().asVar();
+                Var realVar = (subs.containsKey(givenVar))
+                        ? subs.get(givenVar)