[seam-commits] Seam SVN: r10822 - in examples/trunk: seamspace and 31 other directories.

seam-commits at lists.jboss.org seam-commits at lists.jboss.org
Wed May 6 21:14:39 EDT 2009


Author: shane.bryzak at jboss.com
Date: 2009-05-06 21:14:39 -0400 (Wed, 06 May 2009)
New Revision: 10822

Added:
   examples/trunk/seamspace/
   examples/trunk/seamspace/ear/
   examples/trunk/seamspace/ear/pom.xml
   examples/trunk/seamspace/ear/src/
   examples/trunk/seamspace/ear/src/main/
   examples/trunk/seamspace/ear/src/main/application/
   examples/trunk/seamspace/ear/src/main/seam-space-ds.xml
   examples/trunk/seamspace/ejb-jar/
   examples/trunk/seamspace/ejb-jar/pom.xml
   examples/trunk/seamspace/ejb-jar/src/
   examples/trunk/seamspace/ejb-jar/src/main/
   examples/trunk/seamspace/ejb-jar/src/main/java/
   examples/trunk/seamspace/ejb-jar/src/main/java/org/
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/AccountPermission.java
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/AuthenticationEvents.java
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/BlogAction.java
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/BlogComment.java
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/CommentAction.java
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/ContentAction.java
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/ContentServlet.java
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/FriendAction.java
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/FriendComment.java
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/Hash.java
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/HashGenerator.java
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/ImagePermission.java
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/Member.java
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/MemberAccount.java
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/MemberBlog.java
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/MemberFriend.java
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/MemberImage.java
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/MemberRole.java
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/PictureAction.java
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/PictureSearch.java
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/ProfileAction.java
   examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/RegisterAction.java
   examples/trunk/seamspace/ejb-jar/src/main/resources/
   examples/trunk/seamspace/ejb-jar/src/main/resources/META-INF/
   examples/trunk/seamspace/ejb-jar/src/main/resources/META-INF/beans.xml
   examples/trunk/seamspace/ejb-jar/src/main/resources/META-INF/persistence.xml
   examples/trunk/seamspace/ejb-jar/src/test/
   examples/trunk/seamspace/ejb-jar/src/test/java/
   examples/trunk/seamspace/ejb-jar/src/test/java/org/
   examples/trunk/seamspace/ejb-jar/src/test/java/org/jboss/
   examples/trunk/seamspace/ejb-jar/src/test/java/org/jboss/seam/
   examples/trunk/seamspace/ejb-jar/src/test/java/org/jboss/seam/example/
   examples/trunk/seamspace/ejb-jar/src/test/java/org/jboss/seam/example/seamspace/
   examples/trunk/seamspace/ejb-jar/src/test/java/org/jboss/seam/example/seamspace/test/
   examples/trunk/seamspace/ejb-jar/src/test/java/org/jboss/seam/example/seamspace/test/BlogTest.java
   examples/trunk/seamspace/ejb-jar/src/test/java/org/jboss/seam/example/seamspace/test/RegisterTest.java
   examples/trunk/seamspace/ejb-jar/src/test/java/org/jboss/seam/example/seamspace/test/testng.xml
   examples/trunk/seamspace/ejb-jar/src/test/resources/
   examples/trunk/seamspace/ejb-jar/src/test/resources/test-suite.xml
   examples/trunk/seamspace/pom.xml
   examples/trunk/seamspace/readme.txt
   examples/trunk/seamspace/war/
   examples/trunk/seamspace/war/pom.xml
   examples/trunk/seamspace/war/src/
   examples/trunk/seamspace/war/src/main/
   examples/trunk/seamspace/war/src/main/webapp/
   examples/trunk/seamspace/war/src/main/webapp/WEB-INF/
   examples/trunk/seamspace/war/src/main/webapp/WEB-INF/faces-config.xml
   examples/trunk/seamspace/war/src/main/webapp/WEB-INF/web.xml
   examples/trunk/seamspace/war/src/main/webapp/blog.xhtml
   examples/trunk/seamspace/war/src/main/webapp/blogentry.xhtml
   examples/trunk/seamspace/war/src/main/webapp/comment.xhtml
   examples/trunk/seamspace/war/src/main/webapp/createBlog.xhtml
   examples/trunk/seamspace/war/src/main/webapp/favicon.ico
   examples/trunk/seamspace/war/src/main/webapp/friendcomment.xhtml
   examples/trunk/seamspace/war/src/main/webapp/friendrequest.xhtml
   examples/trunk/seamspace/war/src/main/webapp/hashgen.xhtml
   examples/trunk/seamspace/war/src/main/webapp/home.xhtml
   examples/trunk/seamspace/war/src/main/webapp/imagepermissiondetail.page.xml
   examples/trunk/seamspace/war/src/main/webapp/imagepermissiondetail.xhtml
   examples/trunk/seamspace/war/src/main/webapp/imagepermissions.page.xml
   examples/trunk/seamspace/war/src/main/webapp/imagepermissions.xhtml
   examples/trunk/seamspace/war/src/main/webapp/images/
   examples/trunk/seamspace/war/src/main/webapp/images/bg_button.png
   examples/trunk/seamspace/war/src/main/webapp/images/checkmark.png
   examples/trunk/seamspace/war/src/main/webapp/images/cross.png
   examples/trunk/seamspace/war/src/main/webapp/images/ellipsis.png
   examples/trunk/seamspace/war/src/main/webapp/images/no_image.png
   examples/trunk/seamspace/war/src/main/webapp/index.html
   examples/trunk/seamspace/war/src/main/webapp/lightbox/
   examples/trunk/seamspace/war/src/main/webapp/lightbox/builder.js
   examples/trunk/seamspace/war/src/main/webapp/lightbox/bullet.gif
   examples/trunk/seamspace/war/src/main/webapp/lightbox/close.gif
   examples/trunk/seamspace/war/src/main/webapp/lightbox/closelabel.gif
   examples/trunk/seamspace/war/src/main/webapp/lightbox/effects.js
   examples/trunk/seamspace/war/src/main/webapp/lightbox/lightbox.css
   examples/trunk/seamspace/war/src/main/webapp/lightbox/lightbox.js
   examples/trunk/seamspace/war/src/main/webapp/lightbox/loading.gif
   examples/trunk/seamspace/war/src/main/webapp/lightbox/nextlabel.gif
   examples/trunk/seamspace/war/src/main/webapp/lightbox/prevlabel.gif
   examples/trunk/seamspace/war/src/main/webapp/lightbox/prototype.js
   examples/trunk/seamspace/war/src/main/webapp/lightbox/scriptaculous.js
   examples/trunk/seamspace/war/src/main/webapp/pictures.xhtml
   examples/trunk/seamspace/war/src/main/webapp/pictureupload.xhtml
   examples/trunk/seamspace/war/src/main/webapp/profile.xhtml
   examples/trunk/seamspace/war/src/main/webapp/register.xhtml
   examples/trunk/seamspace/war/src/main/webapp/register2.xhtml
   examples/trunk/seamspace/war/src/main/webapp/roledetail.page.xml
   examples/trunk/seamspace/war/src/main/webapp/roledetail.xhtml
   examples/trunk/seamspace/war/src/main/webapp/rolemanager.page.xml
   examples/trunk/seamspace/war/src/main/webapp/rolemanager.xhtml
   examples/trunk/seamspace/war/src/main/webapp/security.xhtml
   examples/trunk/seamspace/war/src/main/webapp/security_error.xhtml
   examples/trunk/seamspace/war/src/main/webapp/style/
   examples/trunk/seamspace/war/src/main/webapp/style/advertising.png
   examples/trunk/seamspace/war/src/main/webapp/style/btn_newpermission.png
   examples/trunk/seamspace/war/src/main/webapp/style/btn_newrole.png
   examples/trunk/seamspace/war/src/main/webapp/style/btn_newuser.png
   examples/trunk/seamspace/war/src/main/webapp/style/cal-next.png
   examples/trunk/seamspace/war/src/main/webapp/style/cal-prev.png
   examples/trunk/seamspace/war/src/main/webapp/style/date.css
   examples/trunk/seamspace/war/src/main/webapp/style/divider.png
   examples/trunk/seamspace/war/src/main/webapp/style/manage_roles.png
   examples/trunk/seamspace/war/src/main/webapp/style/manage_users.png
   examples/trunk/seamspace/war/src/main/webapp/style/padlock.png
   examples/trunk/seamspace/war/src/main/webapp/style/seamspace.css
   examples/trunk/seamspace/war/src/main/webapp/style/seamspace.png
   examples/trunk/seamspace/war/src/main/webapp/style/security.css
   examples/trunk/seamspace/war/src/main/webapp/style/table_header.png
   examples/trunk/seamspace/war/src/main/webapp/style/trash.png
   examples/trunk/seamspace/war/src/main/webapp/template.xhtml
   examples/trunk/seamspace/war/src/main/webapp/userdetail.page.xml
   examples/trunk/seamspace/war/src/main/webapp/userdetail.xhtml
   examples/trunk/seamspace/war/src/main/webapp/usermanager.page.xml
   examples/trunk/seamspace/war/src/main/webapp/usermanager.xhtml
   examples/trunk/seamspace/war/src/main/webapp/welcome.xhtml
Log:
initial load of seamspace example

Added: examples/trunk/seamspace/ear/pom.xml
===================================================================
--- examples/trunk/seamspace/ear/pom.xml	                        (rev 0)
+++ examples/trunk/seamspace/ear/pom.xml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,222 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+   <modelVersion>4.0.0</modelVersion>
+
+   <parent>
+      <groupId>org.jboss.seam.examples</groupId>
+      <artifactId>seam-space</artifactId>
+      <version>3.0.0-SNAPSHOT</version>
+   </parent>
+
+   <artifactId>seam-space-ear</artifactId>
+   <packaging>ear</packaging>
+   <name>SeamSpace Security Example (Application module)</name>
+
+   <build>
+      <defaultGoal>package</defaultGoal>
+      <finalName>${project.parent.artifactId}</finalName>
+      <plugins>
+
+         <plugin>
+            <groupId>org.codehaus.mojo</groupId>
+            <artifactId>jboss-maven-plugin</artifactId>
+            <configuration>
+               <jbossHome>${jboss.home}</jbossHome>
+            </configuration>
+         </plugin>
+
+         <plugin>
+            <artifactId>maven-ear-plugin</artifactId>
+            <configuration>
+               <defaultJavaBundleDir>lib</defaultJavaBundleDir>
+               <jboss>
+                  <version>4.2</version>
+                  <!-- loader-repository gets added automatically by Web Beans -->
+                  <!--<loader-repository>${project.groupId}:loader=${project.build.finalName}</loader-repository>-->
+                  <data-sources>
+                     <data-source>${project.parent.artifactId}-ds.xml</data-source>
+                  </data-sources>
+               </jboss>
+               <modules>
+                  <webModule>
+                     <groupId>${project.groupId}</groupId>
+                     <artifactId>seam-space-war</artifactId>
+                     <contextRoot>/seam-space</contextRoot>
+                  </webModule>
+               </modules>
+               <!-- unpackTypes works incrementally, so I don't have to sync from the ejb-jar/target and war/target in antrun -->
+               <!--<unpackTypes>war,ejb</unpackTypes>-->
+               <version>5</version>
+            </configuration>
+         </plugin>
+
+      </plugins>
+   </build>
+
+   <profiles>
+
+      <profile>
+         <id>explode</id>
+         <build>
+            <plugins>
+               <plugin>
+                  <artifactId>maven-antrun-plugin</artifactId>
+                  <executions>
+                     <execution>
+                        <id>explode-to-jboss-as</id>
+                        <phase>package</phase>
+                        <configuration>
+                           <tasks>
+                              <property name="archive.name" value="${project.build.finalName}"/>
+                              <property name="ear.staging.dir" value="${project.build.directory}/${archive.name}"/>
+                              <property name="ejb-jar.staging.dir" value="../ejb-jar/target/classes"/>
+                              <property name="war.staging.dir" value="../war/target/${archive.name}"/>
+
+                              <property name="ear.deploy.dir" value="${jboss.home}/server/${jboss.domain}/deploy/${archive.name}.ear"/>
+                              <property name="ejb-jar.deploy.dir" value="${ear.deploy.dir}/${archive.name}-ejb.jar"/>
+                              <property name="war.deploy.dir" value="${ear.deploy.dir}/${archive.name}.war"/>
+
+                              <condition property="deployed">
+                                 <available file="${ear.deploy.dir}"/>
+                              </condition>
+
+                              <mkdir dir="${ear.deploy.dir}"/>
+                              <mkdir dir="${ejb-jar.deploy.dir}"/>
+                              <mkdir dir="${war.deploy.dir}"/>
+
+                              <copy todir="${ejb-jar.deploy.dir}" verbose="false" preservelastmodified="true" includeEmptyDirs="false">
+                                 <fileset dir="${ejb-jar.staging.dir}"/>
+                              </copy>
+
+                              <!-- Read as: if none of the files in EJB-JAR are newer than application.xml, set the property ejb-jar.unchanged -->
+                              <uptodate property="ejb-jar.unchanged" targetfile="${ear.deploy.dir}/META-INF/application.xml">
+                                 <srcfiles dir="${ejb-jar.deploy.dir}" includes="**/*"/>
+                              </uptodate>
+
+                              <copy todir="${war.deploy.dir}" verbose="false" preservelastmodified="true" includeEmptyDirs="false">
+                                 <fileset dir="${war.staging.dir}"/>
+                              </copy>
+
+                              <!-- Read as: if none of the config files in WAR are newer than application.xml, set the property webapp-config.unchanged -->
+                              <uptodate property="webapp-config.unchanged" targetfile="${ear.deploy.dir}/META-INF/application.xml">
+                                 <srcfiles dir="${war.deploy.dir}">
+                                    <include name="WEB-INF/web.xml"/>
+                                 </srcfiles>
+                              </uptodate>
+
+                              <!-- Only touch application.xml if the application is not deployed, a file in EJB-JAR has changed or a webapp config file has changed -->
+                              <condition property="restart">
+                                 <or>
+                                     <not><isset property="deployed"/></not>
+                                     <not><isset property="ejb-jar.unchanged"/></not>
+                                     <not><isset property="webapp-config.unchanged"/></not>
+                                 </or>
+                              </condition>
+
+                              <!-- do a checksum to see if application.xml, jboss-app.xml, or -ds.xml need to be updated -->
+                              <!-- this copy implicitly touches application.xml if the restart property is set since it is generated each time -->
+                              <copy todir="${ear.deploy.dir}" verbose="false" preservelastmodified="true" includeEmptyDirs="false">
+                                 <fileset dir="${ear.staging.dir}">
+                                    <include name="**/*" if="restart"/>
+                                    <exclude name="**/*" unless="restart"/>
+                                    <exclude name="*.war"/>
+                                    <exclude name="*.jar"/>
+                                 </fileset>
+                              </copy>
+                           </tasks>
+                        </configuration>
+                        <goals>
+                           <goal>run</goal>
+                        </goals>
+                     </execution>
+                  </executions>
+               </plugin>
+
+            </plugins>
+         </build>
+      </profile>
+
+      <profile>
+         <id>restart</id>
+         <build>
+            <plugins>
+               <plugin>
+                  <artifactId>maven-antrun-plugin</artifactId>
+                  <executions>
+                     <execution>
+                        <id>restart-on-jboss-as</id>
+                        <phase>validate</phase>
+                        <configuration>
+                           <tasks>
+                              <property name="deploy.dir" value="${jboss.home}/server/${jboss.domain}/deploy"/>
+                              <property name="ear.archive.name" value="${project.build.finalName}.ear"/>
+                              <property name="ear.deploy.dir" value="${deploy.dir}/${ear.archive.name}"/>
+                              <available property="deployed" file="${ear.deploy.dir}" type="file"/>
+                              <available property="exploded" file="${ear.deploy.dir}/META-INF/application.xml" type="file"/>
+                              <touch>
+                                 <fileset dir="${deploy.dir}">
+                                    <include name="${ear.archive.name}" if="deployed"/>
+                                    <include name="${ear.archive.name}/META-INF/application.xml" if="exploded"/>
+                                 </fileset>
+                              </touch>
+                           </tasks>
+                        </configuration>
+                        <goals>
+                           <goal>run</goal>
+                        </goals>
+                     </execution>
+                  </executions>
+               </plugin>
+
+            </plugins>
+         </build>
+      </profile>
+
+      <profile>
+         <id>undeploy</id>
+         <build>
+            <plugins>
+               <plugin>
+                  <artifactId>maven-antrun-plugin</artifactId>
+                  <executions>
+                     <execution>
+                        <id>undeploy-from-jboss-as</id>
+                        <phase>validate</phase>
+                        <configuration>
+                           <tasks>
+                              <property name="ear.deploy.dir" value="${jboss.home}/server/${jboss.domain}/deploy/${project.build.finalName}.ear"/>
+                              <delete dir="${ear.deploy.dir}" quiet="true" failonerror="true"/>
+                              <delete file="${ear.deploy.dir}" quiet="true" failonerror="true"/>
+                           </tasks>
+                        </configuration>
+                        <goals>
+                           <goal>run</goal>
+                        </goals>
+                     </execution>
+                  </executions>
+               </plugin>
+
+            </plugins>
+         </build>
+      </profile>
+
+   </profiles>
+
+   <dependencies>
+
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>${project.parent.artifactId}-ejb</artifactId>
+            <type>ejb</type>
+        </dependency>
+
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>${project.parent.artifactId}-war</artifactId>
+            <type>war</type>
+        </dependency>
+
+   </dependencies>
+</project>

Added: examples/trunk/seamspace/ear/src/main/seam-space-ds.xml
===================================================================
--- examples/trunk/seamspace/ear/src/main/seam-space-ds.xml	                        (rev 0)
+++ examples/trunk/seamspace/ear/src/main/seam-space-ds.xml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE datasources
+   PUBLIC "-//JBoss//DTD JBOSS JCA Config 1.5//EN"
+   "http://www.jboss.org/j2ee/dtd/jboss-ds_1_5.dtd">
+<datasources>
+
+    <local-tx-datasource>
+        <jndi-name>seamspaceDatasource</jndi-name>
+        <use-java-context>false</use-java-context>
+        <connection-url>jdbc:hsqldb:.</connection-url>
+        <driver-class>org.hsqldb.jdbcDriver</driver-class>
+        <user-name>sa</user-name>
+        <password></password>
+    </local-tx-datasource>
+
+</datasources>

Added: examples/trunk/seamspace/ejb-jar/pom.xml
===================================================================
--- examples/trunk/seamspace/ejb-jar/pom.xml	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/pom.xml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,291 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+   <modelVersion>4.0.0</modelVersion>
+
+   <parent>
+      <groupId>org.jboss.seam.examples</groupId>
+      <artifactId>seam-space</artifactId>
+      <version>3.0.0-SNAPSHOT</version>
+   </parent>
+
+   <artifactId>seam-space-ejb</artifactId>
+   <packaging>ejb</packaging>
+   <name>SeamSpace Security Example (EJB module)</name>
+
+   <build>
+      <finalName>${project.artifactId}</finalName>
+      <plugins>
+
+         <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-surefire-plugin</artifactId>
+            <configuration>
+               <suiteXmlFiles>
+                  <suiteXmlFile>src/test/resources/test-suite.xml</suiteXmlFile>
+               </suiteXmlFiles>
+            </configuration>
+         </plugin>
+
+      </plugins>
+   </build>
+
+   <dependencies>
+
+      <dependency>
+         <groupId>org.testng</groupId>
+         <artifactId>testng</artifactId>
+         <classifier>jdk15</classifier>
+         <scope>test</scope>
+      </dependency>
+
+      <dependency>
+         <groupId>${webbeans.groupId}</groupId>
+         <artifactId>webbeans-core-test</artifactId>
+         <scope>test</scope>
+      </dependency>
+
+      <dependency>
+         <groupId>org.jboss.test-harness</groupId>
+         <artifactId>jboss-test-harness</artifactId>
+         <scope>test</scope>
+      </dependency>
+
+      <dependency>
+         <groupId>javax.servlet</groupId>
+         <artifactId>servlet-api</artifactId>
+         <scope>test</scope>
+      </dependency>
+
+      <dependency>
+         <groupId>org.jboss.ejb3</groupId>
+         <artifactId>jboss-ejb3-api</artifactId>
+         <scope>test</scope>
+      </dependency>
+
+      <dependency>
+         <groupId>javax.annotation</groupId>
+         <artifactId>jsr250-api</artifactId>
+         <scope>provided</scope>
+      </dependency>
+
+      <dependency>
+         <groupId>javax.ejb</groupId>
+         <artifactId>ejb-api</artifactId>
+         <scope>provided</scope>
+      </dependency>
+
+      <dependency>
+         <groupId>javax.faces</groupId>
+         <artifactId>jsf-api</artifactId>
+         <scope>provided</scope>
+      </dependency>
+
+      <dependency>
+         <groupId>javax.persistence</groupId>
+         <artifactId>persistence-api</artifactId>
+         <scope>provided</scope>
+      </dependency>
+
+      <dependency>
+         <groupId>javax.validation</groupId>
+         <artifactId>validation-api</artifactId>
+         <scope>provided</scope>
+      </dependency>
+
+      <dependency>
+         <groupId>${seam.groupId}</groupId>
+         <artifactId>seam-faces</artifactId>
+      </dependency>
+      
+      <dependency>
+         <groupId>${seam.groupId}</groupId>
+         <artifactId>seam-security</artifactId>
+      </dependency>
+
+      <dependency>
+         <groupId>${webbeans.groupId}</groupId>
+         <artifactId>jsr299-api</artifactId>
+         <scope>provided</scope>
+      </dependency>
+
+      <dependency>
+         <groupId>${webbeans.groupId}</groupId>
+         <artifactId>webbeans-logging</artifactId>
+         <scope>provided</scope>
+      </dependency>
+
+      <dependency>
+         <groupId>${webbeans.groupId}</groupId>
+         <artifactId>webbeans-logger</artifactId>
+      </dependency>
+
+   </dependencies>
+
+   <profiles>
+      <profile>
+         <id>integration-tests</id>
+         <build>
+            <plugins>
+               <plugin>
+                  <groupId>org.apache.maven.plugins</groupId>
+                  <artifactId>maven-dependency-plugin</artifactId>
+                  <executions>
+                     <execution>
+                        <id>copy-integration-test-dependencies</id>
+                        <phase>generate-test-sources</phase>
+                        <goals>
+                           <goal>copy</goal>
+                        </goals>
+                        <configuration>
+                           <stripVersion>true</stripVersion>
+                           <artifactItems>
+                              <artifactItem>
+                                 <groupId>org.jboss.test-harness</groupId>
+                                 <artifactId>jboss-test-harness</artifactId>
+                                 <version>1.0.0-SNAPSHOT</version> <!-- inheritence isn't working here -->
+                                 <overWrite>true</overWrite>
+                                 <outputDirectory>${project.build.directory}/classes/lib</outputDirectory>
+                              </artifactItem>
+                              <!--
+                              <artifactItem>
+                                 <groupId>javax.el</groupId>
+                                 <artifactId>el-ri</artifactId>
+                                 <overWrite>true</overWrite>
+                                 <outputDirectory>${project.build.directory}/dependency/lib</outputDirectory>
+                              </artifactItem>
+                              -->
+                           </artifactItems>
+                        </configuration>
+                     </execution>
+                  </executions>
+               </plugin>
+               <plugin>
+                  <groupId>org.apache.maven.plugins</groupId>
+                  <artifactId>maven-surefire-plugin</artifactId>
+                  <configuration>
+                     <suiteXmlFiles>
+                        <suiteXmlFile>src/test/resources/test-suite.xml</suiteXmlFile>
+                     </suiteXmlFiles>
+                     <systemProperties>
+                        <property>
+                           <name>jboss.home</name>
+                           <value>${jboss.home}</value>
+                        </property>
+                        <property>
+                           <name>org.jboss.testharness.standalone</name>
+                           <value>false</value>
+                        </property>
+                        <property>
+                           <name>org.jboss.testharness.container.extraConfigurationDir</name>
+                           <value>../../</value>
+                        </property>
+                        <property>
+                           <name>org.jboss.testharness.container.forceRestart</name>
+                           <value>false</value>
+                        </property>
+                        <property>
+                           <name>org.jboss.testharness.runIntegrationTests</name>
+                           <value>true</value>
+                        </property>
+                        <property>
+                           <name>org.jboss.testharness.libraryDirectory</name>
+                           <value>${project.build.directory}/dependency/lib</value>
+                        </property>
+                        <property>
+                           <name>org.jboss.testharness.outputDirectory</name>
+                           <value>${project.build.directory}</value>
+                        </property>
+                     </systemProperties>
+                  </configuration>
+               </plugin>
+            </plugins>
+         </build>
+         <dependencies>
+
+            <!--
+            <dependency>
+               <groupId>javax.servlet</groupId>
+               <artifactId>servlet-api</artifactId>
+               <version>2.5</version>
+               <scope>test</scope>
+            </dependency>
+
+            <dependency>
+               <groupId>org.jboss.ejb3</groupId>
+               <artifactId>jboss-ejb3-api</artifactId>
+               <version>3.1.0-Alpha1</version>
+               <scope>test</scope>
+               <exclusions>
+                  <exclusion>
+                     <artifactId>jboss-jaxrpc</artifactId>
+                     <groupId>jbossws</groupId>
+                  </exclusion>
+                  <exclusion>
+                     <artifactId>jboss-transaction-api</artifactId>
+                     <groupId>org.jboss.javaee</groupId>
+                  </exclusion>
+                  <exclusion>
+                     <artifactId>jboss-jaxrpc</artifactId>
+                     <groupId>jboss.jbossws</groupId>
+                  </exclusion>
+               </exclusions>
+            </dependency>
+            -->
+
+            <dependency>
+               <groupId>org.jboss.test-harness</groupId>
+               <artifactId>jboss-test-harness-jboss-as-50</artifactId>
+               <version>1.0.0-SNAPSHOT</version>
+               <scope>test</scope>
+            </dependency>
+
+         </dependencies>
+      </profile>
+
+      <profile>
+         <id>dump-test-artifacts</id>
+         <build>
+            <plugins>
+               <plugin>
+                  <groupId>org.codehaus.mojo</groupId>
+                  <artifactId>exec-maven-plugin</artifactId>
+                  <executions>
+                     <execution>
+                        <id>generate-test-artifacts</id>
+                        <phase>test-compile</phase>
+                        <goals>
+                           <goal>java</goal>
+                        </goals>
+                     </execution>
+                  </executions>
+                  <configuration>
+                     <mainClass>org.jboss.testharness.api.TCK</mainClass>
+                     <classpathScope>test</classpathScope>
+                     <systemProperties>
+                        <systemProperty>
+                           <key>dumpArtifacts</key>
+                           <value>true</value>
+                        </systemProperty>
+                        <systemProperty>
+                           <key>org.jboss.testharness.testPackage</key>
+                           <value>${project.groupId}</value>
+                        </systemProperty>
+                        <systemProperty>
+                           <key>org.jboss.testharness.outputDirectory</key>
+                           <value>${project.build.directory}/test-artifacts</value>
+                        </systemProperty>
+                        <systemProperty>
+                           <key>org.jboss.testharness.libraryDirectory</key>
+                           <value>${project.build.directory}/dependency/lib</value>
+                        </systemProperty>
+                     </systemProperties>
+                  </configuration>
+               </plugin>
+            </plugins>
+         </build>
+      </profile>
+
+   </profiles>
+</project>

Added: examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/AccountPermission.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/AccountPermission.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/AccountPermission.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,81 @@
+package org.jboss.seam.example.seamspace;
+
+import java.io.Serializable;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+
+import org.jboss.seam.annotations.security.permission.PermissionAction;
+import org.jboss.seam.annotations.security.permission.PermissionDiscriminator;
+import org.jboss.seam.annotations.security.permission.PermissionRole;
+import org.jboss.seam.annotations.security.permission.PermissionTarget;
+import org.jboss.seam.annotations.security.permission.PermissionUser;
+
+ at Entity
+public class AccountPermission implements Serializable
+{
+   private static final long serialVersionUID = -5628863031792429938L;
+   
+   private Integer permissionId;
+   private String recipient;
+   private String target;
+   private String action;
+   private String discriminator;
+   
+   @Id @GeneratedValue
+   public Integer getPermissionId()
+   {
+      return permissionId;
+   }
+   
+   public void setPermissionId(Integer permissionId)
+   {
+      this.permissionId = permissionId;
+   }
+   
+   @PermissionUser 
+   @PermissionRole
+   public String getRecipient()
+   {
+      return recipient;
+   }
+   
+   public void setRecipient(String recipient)
+   {
+      this.recipient = recipient;
+   }
+   
+   @PermissionTarget
+   public String getTarget()
+   {
+      return target;
+   }
+   
+   public void setTarget(String target)
+   {
+      this.target = target;
+   }
+   
+   @PermissionAction
+   public String getAction()
+   {
+      return action;
+   }
+   
+   public void setAction(String action)
+   {
+      this.action = action;
+   }
+   
+   @PermissionDiscriminator
+   public String getDiscriminator()
+   {
+      return discriminator;
+   }
+   
+   public void setDiscriminator(String discriminator)
+   {
+      this.discriminator = discriminator;
+   }
+}

Added: examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/AuthenticationEvents.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/AuthenticationEvents.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/AuthenticationEvents.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,16 @@
+package org.jboss.seam.example.seamspace;
+
+import org.jboss.seam.annotations.Name;
+import org.jboss.seam.annotations.Observer;
+import org.jboss.seam.contexts.Contexts;
+import org.jboss.seam.security.management.JpaIdentityStore;
+
+ at Name("authenticationEvents")
+public class AuthenticationEvents
+{
+   @Observer(JpaIdentityStore.EVENT_USER_AUTHENTICATED)
+   public void loginSuccessful(MemberAccount account)
+   {
+      Contexts.getSessionContext().set("authenticatedMember", account.getMember());
+   }
+}

Added: examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/BlogAction.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/BlogAction.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/BlogAction.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,88 @@
+package org.jboss.seam.example.seamspace;
+
+import static org.jboss.seam.ScopeType.CONVERSATION;
+
+import java.util.ArrayList;
+import java.util.Date;
+
+import javax.persistence.EntityManager;
+import javax.persistence.NoResultException;
+
+import org.jboss.seam.annotations.Begin;
+import org.jboss.seam.annotations.End;
+import org.jboss.seam.annotations.Factory;
+import org.jboss.seam.annotations.In;
+import org.jboss.seam.annotations.Name;
+import org.jboss.seam.annotations.Out;
+import org.jboss.seam.annotations.Scope;
+
+ at Scope(CONVERSATION)
+ at Name("blog")
+public class BlogAction
+{    
+   private String name;   
+   private Integer blogId;
+   
+   @In
+   private EntityManager entityManager;
+   
+   @In(required = false) @Out(required = false)
+   private MemberBlog selectedBlog;
+   
+   @In(required = false)
+   private Member authenticatedMember;
+   
+   /**
+    * Used to read a single blog entry for a member
+    */
+   @Factory("selectedBlog") 
+   @Begin(join=true)
+   public void getBlog()
+   {     
+      try
+      {
+         selectedBlog = (MemberBlog) entityManager.createQuery(
+           "from MemberBlog b where b.blogId = :blogId and b.member.memberName = :memberName")
+           .setParameter("blogId", blogId)
+           .setParameter("memberName", name)
+           .getSingleResult();
+      }
+      catch (NoResultException ex) { }
+   }   
+   
+   @Begin
+   public void createEntry()
+   {
+      selectedBlog = new MemberBlog();              
+   }
+   
+   @End
+   public void saveEntry()
+   {
+      selectedBlog.setMember(authenticatedMember);
+      selectedBlog.setEntryDate(new Date());
+      selectedBlog.setComments(new ArrayList<BlogComment>());
+      
+      entityManager.persist(selectedBlog);
+   }
+   
+   public String getName()
+   {
+      return name;
+   }
+   
+   public void setName(String name)
+   {
+      this.name = name;
+   }
+   
+   public Integer getBlogId()
+   {
+      return blogId;
+   }
+   
+   public void setBlogId(Integer blogId)
+   {
+      this.blogId = blogId;
+   }
+}

Added: examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/BlogComment.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/BlogComment.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/BlogComment.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,94 @@
+package org.jboss.seam.example.seamspace;
+
+import java.io.Serializable;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Transient;
+
+import org.hibernate.validator.NotNull;
+import org.jboss.seam.annotations.Name;
+
+ at Entity
+ at Name("blogComment")
+public class BlogComment implements Serializable
+{
+   private static final long serialVersionUID = 5495139096911872039L;
+
+   private static SimpleDateFormat df = new SimpleDateFormat("EEEE, MMMM d, yyyy 'at' hh:mm a");   
+   
+   private Integer commentId;
+   private MemberBlog blog;
+   private Member commentor;
+   private Date commentDate;
+   private String comment;
+   
+   @Id @GeneratedValue
+   public Integer getCommentId()
+   {
+      return commentId;
+   }
+   
+   public void setCommentId(Integer commentId)
+   {
+      this.commentId = commentId;
+   }   
+   
+   @ManyToOne
+   @JoinColumn(name = "BLOG_ID")
+   public MemberBlog getBlog()
+   {
+      return blog;
+   }
+   
+   public void setBlog(MemberBlog blog)
+   {
+      this.blog = blog;
+   }
+   
+   @NotNull
+   public String getComment()
+   {
+      return comment;
+   }
+   public void setComment(String comment)
+   {
+      this.comment = comment;
+   }
+   
+   @NotNull
+   public Date getCommentDate()
+   {
+      return commentDate;
+   }
+   
+   public void setCommentDate(Date commentDate)
+   {
+      this.commentDate = commentDate;
+   }
+   
+   @Transient
+   public String getFormattedCommentDate()
+   {
+     return df.format(commentDate);  
+   }
+
+   @ManyToOne
+   @JoinColumn(name = "COMMENTOR_ID")
+   public Member getCommentor()
+   {
+      return commentor;
+   }
+   
+   public void setCommentor(Member commentor)
+   {
+      this.commentor = commentor;
+   }
+  
+   
+}

Added: examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/CommentAction.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/CommentAction.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/CommentAction.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,55 @@
+package org.jboss.seam.example.seamspace;
+
+import static org.jboss.seam.ScopeType.CONVERSATION;
+
+import java.util.Date;
+
+import javax.persistence.EntityManager;
+
+import org.jboss.seam.annotations.Begin;
+import org.jboss.seam.annotations.In;
+import org.jboss.seam.annotations.Name;
+import org.jboss.seam.annotations.Scope;
+import org.jboss.seam.annotations.Transactional;
+import org.jboss.seam.annotations.security.Insert;
+import org.jboss.seam.core.Conversation;
+
+ at Scope(CONVERSATION)
+ at Name("commentAction")
+ at Transactional
+public class CommentAction 
+{
+   @In
+   private EntityManager entityManager;
+   
+   private BlogComment comment;     
+   
+   @In(required = false)
+   private Member authenticatedMember;
+   
+   @In(required = false)
+   private MemberBlog selectedBlog;
+   
+   @Begin(nested = true) @Insert(BlogComment.class) 
+   public void createComment()
+   {            
+      comment = new BlogComment();
+      comment.setCommentor(authenticatedMember);              
+      comment.setBlog(selectedBlog);
+   }
+   
+   public void saveComment()
+   {      
+      comment.setCommentDate(new Date());
+      entityManager.persist(comment);
+            
+      entityManager.refresh(selectedBlog);
+      
+      Conversation.instance().end();
+   }    
+   
+   public BlogComment getComment()
+   {
+      return comment;
+   }
+}

Added: examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/ContentAction.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/ContentAction.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/ContentAction.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,27 @@
+package org.jboss.seam.example.seamspace;
+
+import static org.jboss.seam.ScopeType.STATELESS;
+
+import javax.persistence.EntityManager;
+
+import org.jboss.seam.annotations.In;
+import org.jboss.seam.annotations.Name;
+import org.jboss.seam.annotations.Scope;
+import org.jboss.seam.security.Identity;
+
+ at Scope(STATELESS)
+ at Name("contentAction")
+public class ContentAction
+{
+   @In EntityManager entityManager;   
+   
+   public MemberImage getImage(int imageId)
+   {
+      MemberImage img = entityManager.find(MemberImage.class, imageId);
+      
+      if (img == null || !Identity.instance().hasPermission(img, "view"))
+         return null;
+      else
+         return img;
+   }
+}

Added: examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/ContentServlet.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/ContentServlet.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/ContentServlet.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,137 @@
+package org.jboss.seam.example.seamspace;
+
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import javax.imageio.ImageIO;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.swing.ImageIcon;
+
+import org.jboss.seam.Component;
+
+/**
+ * Serves images and other member content
+ * 
+ * @author Shane Bryzak
+ */
+public class ContentServlet extends HttpServlet
+{
+   private static final long serialVersionUID = -8461940507242022217L;
+
+   private static final String IMAGES_PATH = "/images";
+
+   /**
+    * The maximum width allowed for image rescaling
+    */
+   private static final int MAX_IMAGE_WIDTH = 1024;
+   
+   private byte[] noImage;
+   
+   public ContentServlet()
+   {
+      InputStream in = getClass().getResourceAsStream("/images/no_image.png");
+      if (in != null)
+      {
+         ByteArrayOutputStream out = new ByteArrayOutputStream();
+         byte[] buffer = new byte[512];
+         try
+         {
+            int read = in.read(buffer);
+            while (read != -1)
+            {
+               out.write(buffer, 0, read);
+               read = in.read(buffer);
+            }
+            
+            noImage = out.toByteArray();
+         } 
+         catch (IOException e) { }
+      }
+      
+   }
+
+   @Override
+   protected void doGet(HttpServletRequest request, HttpServletResponse response)
+         throws ServletException, IOException
+   {
+      if (IMAGES_PATH.equals(request.getPathInfo()))
+      {
+         ContentAction contentAction = (ContentAction) Component.getInstance(ContentAction.class);
+
+         String id = request.getParameter("id");
+         MemberImage mi = (id != null && !"".equals(id)) ? 
+               contentAction.getImage(Integer.parseInt(id)) : null;
+         
+         String contentType = null;
+         byte[] data = null;
+         
+         if (mi != null && mi.getData() != null && mi.getData().length > 0)
+         {
+            contentType = mi.getContentType();
+            data = mi.getData();
+         }
+         else if (noImage != null)
+         {
+            contentType = "image/png";
+            data = noImage;
+         }
+         
+         if (data != null)
+         {
+            response.setContentType(contentType);
+   
+            boolean rescale = false;
+            int width = 0;
+            ImageIcon icon = null;
+   
+            // Check if the image needs to be rescaled
+            if (request.getParameter("width") != null)
+            {
+               width = Math.min(MAX_IMAGE_WIDTH, Integer.parseInt(request
+                     .getParameter("width")));
+               icon = new ImageIcon(data);
+               if (width > 0 && width != icon.getIconWidth())
+                  rescale = true;
+            }
+   
+            // Rescale the image if required
+            if (rescale)
+            {
+               double ratio = (double) width / icon.getIconWidth();
+               int height = (int) (icon.getIconHeight() * ratio);
+               
+               int imageType = "image/png".equals(contentType) ? 
+                     BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB;                  
+               BufferedImage bImg = new BufferedImage(width, height, imageType);
+               Graphics2D g2d = bImg.createGraphics();
+               g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+                     RenderingHints.VALUE_INTERPOLATION_BICUBIC);
+               g2d.drawImage(icon.getImage(), 0, 0, width, height, null);
+               g2d.dispose();
+   
+               String formatName = "";
+               if (contentType != null && contentType.indexOf("png") != -1)
+                  formatName = "png";
+               else if (contentType != null && (contentType.indexOf("jpg") != -1) ||
+                     contentType.indexOf("jpeg") != -1)
+                  formatName = "jpeg";
+   
+               ImageIO.write(bImg, formatName, response.getOutputStream());
+            }
+            else
+            {
+               response.getOutputStream().write(data);
+            }
+         }
+
+         response.getOutputStream().flush();
+      }
+   }
+}

Added: examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/FriendAction.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/FriendAction.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/FriendAction.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,108 @@
+package org.jboss.seam.example.seamspace;
+
+import static org.jboss.seam.ScopeType.CONVERSATION;
+
+import java.io.Serializable;
+import java.util.Date;
+
+import javax.ejb.Remove;
+import javax.persistence.EntityManager;
+import javax.persistence.NoResultException;
+
+import org.jboss.seam.annotations.Begin;
+import org.jboss.seam.annotations.Destroy;
+import org.jboss.seam.annotations.End;
+import org.jboss.seam.annotations.Factory;
+import org.jboss.seam.annotations.In;
+import org.jboss.seam.annotations.Name;
+import org.jboss.seam.annotations.Out;
+import org.jboss.seam.annotations.Scope;
+import org.jboss.seam.annotations.web.RequestParameter;
+import org.jboss.seam.contexts.Contexts;
+import org.jboss.seam.faces.FacesMessages;
+import org.jboss.seam.security.Identity;
+
+ at Scope(CONVERSATION)
+ at Name("friendAction")
+public class FriendAction implements Serializable
+{
+   private static final long serialVersionUID = 4565339001481077911L;
+
+   @RequestParameter("name")
+   private String name;
+   
+   @Out(required = false)
+   private FriendComment friendComment;
+   
+   @Out(required = false)
+   private MemberFriend friendRequest;
+   
+   @In(required = false)
+   private Member authenticatedMember;
+      
+   @In
+   private EntityManager entityManager;
+      
+   @Factory("friendComment") @Begin
+   public void createComment()
+   {      
+      try
+      {
+         Member member = (Member) entityManager.createQuery(
+         "from Member where memberName = :memberName")
+         .setParameter("memberName", name)
+         .getSingleResult();
+                  
+         Contexts.getMethodContext().set("friends", member.getFriends());
+         Identity.instance().checkPermission(member, "createFriendComment");
+
+         friendComment = new FriendComment();
+         friendComment.setFriend(authenticatedMember);
+         friendComment.setMember(member);
+      }
+      catch (NoResultException ex) 
+      { 
+         FacesMessages.instance().add("Member not found.");
+      }
+   }
+   
+   @End
+   public void saveComment()
+   {
+      friendComment.setCommentDate(new Date());
+      entityManager.persist(friendComment);
+   }
+   
+   @Factory("friendRequest") @Begin
+   public void createRequest()
+   {
+      try
+      {
+         Member member = (Member) entityManager.createQuery(
+         "from Member where memberName = :memberName")
+         .setParameter("memberName", name)
+         .getSingleResult();
+                  
+         Contexts.getMethodContext().set("friends", member.getFriends());
+         Identity.instance().checkPermission(member, "createFriendRequest");
+
+         friendRequest = new MemberFriend();
+         friendRequest.setFriend(authenticatedMember);
+         friendRequest.setMember(member);
+      }
+      catch (NoResultException ex) 
+      { 
+         FacesMessages.instance().add("Member not found.");
+      }
+   }
+
+   @End
+   public void saveRequest()
+   {
+      friendRequest.getMember().getFriends().add(friendRequest);
+      entityManager.persist(friendRequest);      
+   }
+   
+   @Remove @Destroy
+   public void destroy() { }    
+}

Added: examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/FriendComment.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/FriendComment.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/FriendComment.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,91 @@
+package org.jboss.seam.example.seamspace;
+
+import java.io.Serializable;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Transient;
+
+import org.jboss.seam.annotations.Name;
+
+ at Entity
+ at Name("friendComment")
+public class FriendComment implements Serializable
+{
+   private static final long serialVersionUID = -288494386341008371L;
+
+   private static SimpleDateFormat df = new SimpleDateFormat("d MMMM yyyy hh:mm a");   
+   
+   private Integer id;
+   private Member member;
+   private Member friend;
+   private Date commentDate;
+   private String comment;
+   
+   @Id @GeneratedValue
+   public Integer getId()
+   {
+      return id;
+   }
+   
+   public void setId(Integer id)
+   {
+      this.id = id;
+   }   
+   
+   public String getComment()
+   {
+      return comment;
+   }
+   
+   public void setComment(String comment)
+   {
+      this.comment = comment;
+   }
+   
+   public Date getCommentDate()
+   {
+      return commentDate;
+   }
+   
+   public void setCommentDate(Date commentDate)
+   {
+      this.commentDate = commentDate;
+   }
+   
+   @Transient
+   public String getFormattedCommentDate()
+   {
+     return df.format(commentDate);  
+   }   
+   
+   @ManyToOne
+   @JoinColumn(name = "FRIEND_ID")
+   public Member getFriend()
+   {
+      return friend;
+   }
+   
+   public void setFriend(Member friend)
+   {
+      this.friend = friend;
+   }
+      
+   @ManyToOne
+   @JoinColumn(name = "MEMBER_ID")
+   public Member getMember()
+   {
+      return member;
+   }
+   
+   public void setMember(Member member)
+   {
+      this.member = member;
+   }
+
+}

Added: examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/Hash.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/Hash.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/Hash.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,45 @@
+package org.jboss.seam.example.seamspace;
+
+import java.security.MessageDigest;
+
+import org.jboss.seam.Component;
+import org.jboss.seam.annotations.Name;
+import org.jboss.seam.util.Hex;
+
+ at Name("hash")
+public class Hash {
+    String hashFunction = "MD5";
+    String charset      = "UTF-8";
+    
+    public String hash(String password) {
+        try {
+            MessageDigest md = MessageDigest.getInstance(hashFunction);
+            md.update(password.getBytes(charset));
+            byte[] raw = md.digest();
+            return new String(Hex.encodeHex(raw));
+        } 
+        catch (Exception e) {
+            throw new RuntimeException(e);        
+        }
+    }
+
+    public String getCharset() {
+        return charset;
+    }
+
+    public void setCharset(String charset) {
+        this.charset = charset;
+    }
+
+    public String getHashFunction() {
+        return hashFunction;
+    }
+
+    public void setHashFunction(String hashFunction) {
+        this.hashFunction = hashFunction;
+    }    
+    
+    public static Hash instance() {
+        return (Hash) Component.getInstance(Hash.class);
+    }
+}

Added: examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/HashGenerator.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/HashGenerator.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/HashGenerator.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,78 @@
+package org.jboss.seam.example.seamspace;
+
+import org.jboss.seam.ScopeType;
+import org.jboss.seam.annotations.In;
+import org.jboss.seam.annotations.Name;
+import org.jboss.seam.annotations.Scope;
+import org.jboss.seam.security.crypto.BinTools;
+import org.jboss.seam.security.management.JpaIdentityStore;
+import org.jboss.seam.security.management.PasswordHash;
+
+ at Scope(ScopeType.EVENT)
+ at Name("hashgenerator")
+public class HashGenerator
+{
+   @In JpaIdentityStore identityStore;
+   
+   private String password;
+   private String passwordHash;
+   private String passwordSalt;
+   
+   public String getPassword()
+   {
+      return password;
+   }
+   
+   public void setPassword(String password)
+   {
+      this.password = password;
+   }
+   
+   public String getPasswordHash()
+   {
+      return passwordHash;
+   }
+   
+   public void setPasswordHash(String passwordHash)
+   {
+      this.passwordHash = passwordHash;
+   }
+   
+   public String getPasswordSalt()
+   {
+      return passwordSalt;
+   }
+   
+   public void setPasswordSalt(String passwordSalt)
+   {
+      this.passwordSalt = passwordSalt;
+   }
+   
+   public void generate()
+   {
+      byte[] salt;
+      
+      if (passwordSalt == null || "".equals(passwordSalt.trim()))
+      {
+         salt = PasswordHash.instance().generateRandomSalt();
+         passwordSalt = BinTools.bin2hex(salt);
+      }
+      else
+      {
+         salt = BinTools.hex2bin(passwordSalt);
+      }
+      
+      passwordHash = identityStore.generatePasswordHash(password, salt);
+   }
+   
+   public String getSql()
+   {
+      StringBuilder sb = new StringBuilder();
+      sb.append("INSERT INTO USER_ACCOUNT (username, password_hash, password_salt) values ('johnsmith', '");
+      sb.append(passwordHash);
+      sb.append("', '");
+      sb.append(passwordSalt);
+      sb.append("');");      
+      return sb.toString();
+   }
+}

Added: examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/ImagePermission.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/ImagePermission.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/ImagePermission.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,200 @@
+package org.jboss.seam.example.seamspace;
+
+import static org.jboss.seam.ScopeType.CONVERSATION;
+
+import java.io.Serializable;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.persistence.EntityManager;
+
+import org.jboss.seam.annotations.Begin;
+import org.jboss.seam.annotations.In;
+import org.jboss.seam.annotations.Name;
+import org.jboss.seam.annotations.Scope;
+import org.jboss.seam.core.Conversation;
+import org.jboss.seam.faces.FacesMessages;
+import org.jboss.seam.security.Role;
+import org.jboss.seam.security.SimplePrincipal;
+import org.jboss.seam.security.management.IdentityManager;
+import org.jboss.seam.security.permission.Permission;
+import org.jboss.seam.security.permission.PermissionManager;
+import org.jboss.seam.security.permission.action.PermissionSearch;
+
+ at Name("imagePermission")
+ at Scope(CONVERSATION)
+public class ImagePermission implements Serializable
+{
+   private static final long serialVersionUID = -4943654157860780587L;
+
+   private List<String> selectedRoles;   
+   private List<Member> selectedFriends;
+   private List<String> selectedActions;
+   
+   private List<String> originalActions;
+   
+   private List<Member> availableFriends;   
+   
+   @In IdentityManager identityManager;
+   @In PermissionManager permissionManager;
+   
+   @In EntityManager entityManager;
+   
+   @In PermissionSearch permissionSearch;   
+   
+   private MemberImage target; 
+   
+   private Principal recipient;
+   
+   @SuppressWarnings("unchecked")
+   @Begin(nested = true)
+   public void createPermission()
+   {
+      target = (MemberImage) permissionSearch.getTarget();
+      
+      selectedFriends = new ArrayList<Member>();
+      
+      availableFriends = entityManager.createQuery(
+            "select f.friend from MemberFriend f where f.member = :member and f.authorized = true")
+            .setParameter("member", target.getMember())
+            .getResultList();      
+   }
+   
+   @Begin(nested = true)
+   public void editPermission()
+   {
+      target = (MemberImage) permissionSearch.getTarget();
+      recipient = permissionSearch.getSelectedRecipient();
+            
+      List<Permission> permissions = permissionManager.listPermissions(target);
+      
+      selectedActions = new ArrayList<String>();      
+      
+      for (Permission permission : permissions)
+      {
+         if (permission.getRecipient().equals(recipient))
+         {
+            if (!selectedActions.contains(permission.getAction()))
+            {
+               selectedActions.add(permission.getAction());
+            }
+         }
+      }
+      
+      originalActions = new ArrayList<String>(selectedActions);
+   }
+
+   public List<String> getSelectedRoles()
+   {
+      return selectedRoles;
+   }
+   
+   public void setSelectedRoles(List<String> selectedRoles)
+   {
+      this.selectedRoles = selectedRoles;
+   }
+   
+   public List<Member> getSelectedFriends()
+   {
+      return selectedFriends;
+   }
+   
+   public void setSelectedFriends(List<Member> selectedFriends)
+   {
+      this.selectedFriends = selectedFriends;
+   }
+   
+   public List<String> getSelectedActions()
+   {
+      return selectedActions;
+   }
+   
+   public void setSelectedActions(List<String> selectedActions)
+   {
+      this.selectedActions = selectedActions;
+   }
+   
+   public String applyPermissions()
+   {
+      // If the recipient isn't null, it means we're editing existing permissions
+      if (recipient != null)
+      {
+         List<Permission> grantedPermissions = new ArrayList<Permission>();
+         List<Permission> revokedPermissions = new ArrayList<Permission>();
+         
+         for (String action : selectedActions)
+         {
+            if (!originalActions.contains(action)) 
+            {
+               grantedPermissions.add(new Permission(target, action, recipient));
+            }
+         }
+         
+         for (String action : originalActions)
+         {
+            if (!selectedActions.contains(action))
+            {
+               revokedPermissions.add(new Permission(target, action, recipient));
+            }
+         }
+         
+         if (!grantedPermissions.isEmpty()) permissionManager.grantPermissions(grantedPermissions);
+         if (!revokedPermissions.isEmpty()) permissionManager.revokePermissions(revokedPermissions);
+      }
+      // otherwise this is a set of new permissions
+      else
+      {
+         if (selectedActions.size() == 0)
+         {
+            FacesMessages.instance().add("You must select at least one action");
+            return "failure";
+         }
+         
+         List<Permission> permissions = new ArrayList<Permission>();
+   
+         for (String role : selectedRoles)
+         {
+            Principal r = new Role(role);
+            for (String action : selectedActions)
+            {            
+               permissions.add(new Permission(target, action, r));
+            }
+         }
+         
+         for (Member friend : selectedFriends)
+         {
+            MemberAccount acct = (MemberAccount) entityManager.createQuery(
+                  "select a from MemberAccount a where a.member = :member")
+                  .setParameter("member", friend)
+                  .getSingleResult();
+            
+            Principal p = new SimplePrincipal(acct.getUsername());
+            
+            for (String action : selectedActions)
+            {
+               permissions.add(new Permission(target, action, p));
+            }
+         }
+         
+         permissionManager.grantPermissions(permissions);
+      }
+      Conversation.instance().endBeforeRedirect();
+      return "success";
+   }
+   
+   public List<Member> getAvailableFriends()
+   {
+      return availableFriends;
+   }
+   
+   public MemberImage getTarget()
+   {
+      return target;
+   }
+   
+   public Principal getRecipient()
+   {
+      return recipient;
+   }
+}

Added: examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/Member.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/Member.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/Member.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,252 @@
+package org.jboss.seam.example.seamspace;
+
+import java.io.Serializable;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Set;
+
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.Transient;
+import javax.persistence.UniqueConstraint;
+
+import org.hibernate.validator.Email;
+import org.hibernate.validator.Length;
+import org.hibernate.validator.NotNull;
+import org.hibernate.validator.Pattern;
+import org.jboss.seam.annotations.Name;
+
+/**
+ * A member account
+ * 
+ * @author Shane Bryzak
+ */
+ at Entity
+ at Name("member")
+ at Table(uniqueConstraints = @UniqueConstraint(columnNames = "membername"))
+public class Member implements Serializable
+{
+   private static final long serialVersionUID = 5179242727836683375L;
+   
+   public enum Gender {
+      male("Male", "his"), 
+      female("Female", "her");
+      
+     private String descr;
+     private String possessive;
+     
+     Gender(String descr, String possessive) {
+       this.descr = descr;
+       this.possessive = possessive;
+      }
+     public String getDescr() {
+        return descr;
+     }
+     
+     public String getPossessive() {
+        return possessive;
+     }
+   };
+   
+   private Integer memberId;
+   private String memberName;
+   private String firstName;
+   private String lastName;
+   private String email;
+   private MemberImage picture;
+   
+   private String tagline;
+   private Gender gender;
+   private Date dob;
+   private String location;
+   private Date memberSince;
+   
+   private Set<MemberImage> images;   
+   private Set<MemberFriend> friends;
+
+   @Id @GeneratedValue
+   public Integer getMemberId()
+   {
+      return memberId;
+   }
+
+   public void setMemberId(Integer memberId)
+   {
+      this.memberId = memberId;
+   }
+   
+   @NotNull
+   @Length(min = 3, max = 40)
+   @Pattern(regex="[a-zA-Z]?[a-zA-Z0-9_]+", 
+         message="Member name must start with a letter, and only contain letters, numbers or underscores")
+   public String getMemberName()
+   {
+      return memberName;
+   }
+
+   public void setMemberName(String memberName)
+   {
+      this.memberName = memberName;
+   }
+   
+   @NotNull
+   @Length(min = 3, max = 40)
+   @Pattern(regex="[a-zA-Z]+", message="First name must only contain letters")
+   public String getFirstName()
+   {
+      return firstName;
+   }
+   
+   public void setFirstName(String firstName)
+   {
+      this.firstName = firstName;
+   }
+   
+   @NotNull
+   @Length(min = 3, max = 40)
+   @Pattern(regex="[a-zA-Z]+", message="Last name must only contain letters")
+   public String getLastName()
+   {
+      return lastName;
+   }
+   
+   public void setLastName(String lastName)
+   {
+      this.lastName = lastName;
+   }   
+   
+   @NotNull @Email
+   public String getEmail()
+   {
+      return email;
+   }
+   
+   public void setEmail(String email)
+   {
+      this.email = email;
+   }
+
+   @OneToOne(fetch = FetchType.LAZY)
+   @JoinColumn(name = "PICTURE_ID")
+   public MemberImage getPicture()
+   {
+      return picture;
+   }
+
+   public void setPicture(MemberImage picture)
+   {
+      this.picture = picture;
+   }
+
+   @NotNull
+   public Date getDob()
+   {
+      return dob;
+   }
+
+   public void setDob(Date dob)
+   {
+      this.dob = dob;
+   }
+
+   @NotNull
+   public Gender getGender()
+   {
+      return gender;
+   }
+
+   public void setGender(Gender gender)
+   {
+      this.gender = gender;
+   }
+
+   public String getLocation()
+   {
+      return location;
+   }
+
+   public void setLocation(String location)
+   {
+      this.location = location;
+   }
+   
+   @NotNull
+   public Date getMemberSince()
+   {
+      return memberSince;
+   }
+   
+   public void setMemberSince(Date memberSince)
+   {
+      this.memberSince = memberSince;
+   }
+
+   public String getTagline()
+   {
+      return tagline;
+   }
+
+   public void setTagline(String tagline)
+   {
+      this.tagline = tagline;
+   }
+
+   @OneToMany(mappedBy = "member", fetch = FetchType.LAZY)
+   public Set<MemberImage> getImages()
+   {
+      return images;
+   }
+
+   public void setImages(Set<MemberImage> images)
+   {
+      this.images = images;
+   }
+   
+   @OneToMany(mappedBy = "member")
+   public Set<MemberFriend> getFriends()
+   {
+      return friends;
+   }
+   
+   public void setFriends(Set<MemberFriend> friends)
+   {
+      this.friends = friends;   
+   }
+   
+   @Transient
+   public boolean isFriend(Member member)
+   {
+      for (MemberFriend f : friends)
+      {
+         if (f.getFriend().getMemberId().equals(member.getMemberId())) return true;          
+      }
+      
+      return false;
+   }
+   
+   @Transient
+   public String getAge()
+   {
+      Calendar birthday = new GregorianCalendar();
+      birthday.setTime(dob);
+      int by = birthday.get(Calendar.YEAR);
+      int bm = birthday.get(Calendar.MONTH);
+      int bd = birthday.get(Calendar.DATE);
+      
+      Calendar now = new GregorianCalendar();
+      now.setTimeInMillis(System.currentTimeMillis());
+      int ny = now.get(Calendar.YEAR);
+      int nm = now.get(Calendar.MONTH);
+      int nd = now.get(Calendar.DATE);      
+      
+      int age = ny - by + (nm > bm || (nm == bm && nd >= bd) ? 0 : -1);                              
+      return String.format("%d years old", age);                              
+   }
+}

Added: examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/MemberAccount.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/MemberAccount.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/MemberAccount.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,120 @@
+package org.jboss.seam.example.seamspace;
+
+import java.io.Serializable;
+import java.util.Set;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+
+import org.hibernate.validator.NotNull;
+import org.jboss.seam.annotations.security.management.PasswordSalt;
+import org.jboss.seam.annotations.security.management.UserEnabled;
+import org.jboss.seam.annotations.security.management.UserPassword;
+import org.jboss.seam.annotations.security.management.UserPrincipal;
+import org.jboss.seam.annotations.security.management.UserRoles;
+
+ at Entity
+ at Table(uniqueConstraints = @UniqueConstraint(columnNames = "username"))
+public class MemberAccount implements Serializable
+{
+   private static final long serialVersionUID = 6368734442192368866L;
+   
+   private Integer accountId;
+   private String username;
+   private String passwordHash;
+   private String passwordSalt;
+   private boolean enabled;   
+   
+   private Set<MemberRole> roles;
+   private Member member;   
+   
+   @Id @GeneratedValue
+   public Integer getAccountId()
+   {
+      return accountId;
+   }
+   
+   public void setAccountId(Integer accountId)
+   {
+      this.accountId = accountId;
+   }
+   
+   @NotNull @UserPrincipal
+   public String getUsername()
+   {
+      return username;
+   }
+   
+   public void setUsername(String username)
+   {
+      this.username = username;
+   }
+   
+   @UserPassword
+   public String getPasswordHash()
+   {
+      return passwordHash;
+   }
+   
+   public void setPasswordHash(String passwordHash)
+   {
+      this.passwordHash = passwordHash;      
+   }
+   
+   @PasswordSalt
+   public String getPasswordSalt()
+   {
+      return passwordSalt;
+   }
+   
+   public void setPasswordSalt(String passwordSalt)
+   {
+      this.passwordSalt = passwordSalt;
+   }
+   
+   @UserEnabled
+   public boolean isEnabled()
+   {
+      return enabled;
+   }
+
+   public void setEnabled(boolean enabled)
+   {
+      this.enabled = enabled;      
+   }   
+
+   @UserRoles
+   @ManyToMany(targetEntity = MemberRole.class)
+   @JoinTable(name = "AccountMembership", 
+         joinColumns = @JoinColumn(name = "AccountId"),
+         inverseJoinColumns = @JoinColumn(name = "MemberOf")
+      )
+   public Set<MemberRole> getRoles()
+   {
+      return roles;
+   }
+   
+   public void setRoles(Set<MemberRole> roles)
+   {
+      this.roles = roles;
+   }
+   
+   @OneToOne
+   @JoinColumn(name = "MEMBER_ID")
+   public Member getMember()
+   {
+      return member;
+   }
+   
+   public void setMember(Member member)
+   {
+      this.member = member;
+   }
+}

Added: examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/MemberBlog.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/MemberBlog.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/MemberBlog.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,134 @@
+package org.jboss.seam.example.seamspace;
+
+import java.io.Serializable;
+import java.text.SimpleDateFormat;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.PrePersist;
+import javax.persistence.Transient;
+
+import org.jboss.seam.annotations.Name;
+import org.jboss.seam.annotations.security.Restrict;
+
+ at Entity
+ at Name("memberBlog")
+public class MemberBlog implements Serializable
+{
+   private static final long serialVersionUID = 7824113911888715595L;
+   
+   private static SimpleDateFormat df = new SimpleDateFormat("EEEE, MMMM d, yyyy - hh:mm a");
+   
+   private Integer blogId;
+   private Member member;
+   private Date entryDate;
+   private String title;
+   private String text;
+   
+   private List<BlogComment> comments;
+   
+   /**
+    * This is an example of a security restriction.  Any attempts to persist a
+    * new memberBlog instance requires the user to pass a permission check.  In 
+    * this case, because the method is annotated with <code>@PrePersist</code> 
+    * the required permission is memberBlog:insert    
+    */
+   @PrePersist @Restrict
+   public void prePersist() {}
+   
+   @Id @GeneratedValue
+   public Integer getBlogId()
+   {
+      return blogId;
+   }
+   
+   public void setBlogId(Integer blogId)
+   {
+      this.blogId = blogId;
+   }
+
+   public Date getEntryDate()
+   {
+      return entryDate;
+   }
+
+   public void setEntryDate(Date entryDate)
+   {
+      this.entryDate = entryDate;
+   }
+   
+   @Transient
+   public String getFormattedEntryDate()
+   {
+      return df.format(entryDate);
+   }
+
+   @ManyToOne
+   @JoinColumn(name = "MEMBER_ID")   
+   public Member getMember()
+   {
+      return member;
+   }
+
+   public void setMember(Member member)
+   {
+      this.member = member;
+   }
+
+   public String getText()
+   {
+      return text;
+   }
+
+   public void setText(String text)
+   {
+      this.text = text;
+   }
+
+   public String getTitle()
+   {
+      return title;
+   }
+
+   public void setTitle(String title)
+   {
+      this.title = title;
+   }
+   
+   @OneToMany(mappedBy = "blog")
+   public List<BlogComment> getComments()
+   {
+      return comments;
+   }
+   
+   public void setComments(List<BlogComment> comments)
+   {
+      this.comments = comments;
+   }
+   
+   @Transient
+   public List<BlogComment> getSortedComments()
+   {
+      Collections.sort(comments, new Comparator<BlogComment>() {
+         public int compare(BlogComment o1, BlogComment o2) {
+            return (int) (o1.getCommentDate().getTime() - o2.getCommentDate().getTime());
+         }
+      });
+      
+      return comments;
+   }
+   
+   @Transient
+   public int getCommentCount()
+   {
+      return comments.size();
+   }
+}

Added: examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/MemberFriend.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/MemberFriend.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/MemberFriend.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,92 @@
+package org.jboss.seam.example.seamspace;
+
+import java.io.Serializable;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+
+import org.jboss.seam.annotations.Name;
+
+ at Entity
+ at Name("memberFriend")
+public class MemberFriend implements Serializable
+{
+   private static final long serialVersionUID = -167586088947004386L;
+   
+   private Integer id;
+   private Member member;
+   private Member friend;
+   
+   private String introduction;
+   private String response;
+   
+   private boolean authorized;
+
+   @Id @GeneratedValue
+   public Integer getId()
+   {
+      return id;
+   }
+   
+   public void setId(Integer id)
+   {
+      this.id = id;
+   }   
+   
+   public boolean isAuthorized()
+   {
+      return authorized;
+   }
+   
+   public void setAuthorized(boolean authorized)
+   {
+      this.authorized = authorized;
+   }
+   
+   @ManyToOne
+   @JoinColumn(name = "FRIEND_ID")
+   public Member getFriend()
+   {
+      return friend;
+   }
+   
+   public void setFriend(Member friend)
+   {
+      this.friend = friend;
+   }
+
+   @ManyToOne
+   @JoinColumn(name = "MEMBER_ID")
+   public Member getMember()
+   {
+      return member;
+   }
+   
+   public void setMember(Member member)
+   {
+      this.member = member;
+   }
+
+   public String getIntroduction()
+   {
+      return introduction;
+   }
+
+   public void setIntroduction(String introduction)
+   {
+      this.introduction = introduction;
+   }
+
+   public String getResponse()
+   {
+      return response;
+   }
+
+   public void setResponse(String response)
+   {
+      this.response = response;
+   }      
+}

Added: examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/MemberImage.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/MemberImage.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/MemberImage.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,84 @@
+package org.jboss.seam.example.seamspace;
+
+import java.io.Serializable;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.Lob;
+import javax.persistence.ManyToOne;
+
+import org.jboss.seam.annotations.security.permission.Permission;
+import org.jboss.seam.annotations.security.permission.Permissions;
+
+ at Permissions({
+   @Permission(action = "view"),
+   @Permission(action = "comment")
+})
+ at Entity
+public class MemberImage implements Serializable
+{
+   private static final long serialVersionUID = -8088455267213832920L;
+   
+   private Integer imageId;
+   private Member member;
+   private byte[] data;
+   private String contentType;
+   private String caption;
+   
+   @Id @GeneratedValue
+   public Integer getImageId()
+   {
+      return imageId;
+   }
+   
+   public void setImageId(Integer imageId)
+   {
+      this.imageId = imageId;
+   }
+   
+   @ManyToOne
+   @JoinColumn(name = "MEMBER_ID")
+   public Member getMember()
+   {
+      return member;
+   }
+   
+   public void setMember(Member member)
+   {
+      this.member = member;
+   }
+
+   public String getContentType()
+   {
+      return contentType;
+   }
+
+   public void setContentType(String contentType)
+   {
+      this.contentType = contentType;
+   }
+   
+   public String getCaption()
+   {
+      return caption;
+   }
+   
+   public void setCaption(String caption)
+   {
+      this.caption = caption;
+   }
+
+   @Lob
+   public byte[] getData()
+   {
+      return data;
+   }
+
+   public void setData(byte[] data)
+   {
+      this.data = data;
+   }
+
+}

Added: examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/MemberRole.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/MemberRole.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/MemberRole.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,76 @@
+package org.jboss.seam.example.seamspace;
+
+import java.io.Serializable;
+import java.util.Set;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+
+import org.jboss.seam.annotations.security.management.RoleConditional;
+import org.jboss.seam.annotations.security.management.RoleGroups;
+import org.jboss.seam.annotations.security.management.RoleName;
+
+ at Entity
+public class MemberRole implements Serializable
+{
+   private static final long serialVersionUID = 9177366120789064801L;
+   
+   private Integer roleId;
+   private String name;
+   private boolean conditional;
+   
+   private Set<MemberRole> groups;
+   
+   @Id @GeneratedValue
+   public Integer getRoleId()
+   {
+      return roleId;
+   }
+   
+   public void setRoleId(Integer roleId)
+   {
+      this.roleId = roleId;
+   }
+   
+   @RoleName
+   public String getName()
+   {
+      return name;
+   }
+   
+   public void setName(String name)
+   {
+      this.name = name;
+   }
+   
+   @RoleGroups
+   @ManyToMany(targetEntity = MemberRole.class)
+   @JoinTable(name = "RoleGroup", 
+         joinColumns = @JoinColumn(name = "RoleId"),
+         inverseJoinColumns = @JoinColumn(name = "MemberOf")
+      )
+   public Set<MemberRole> getGroups()
+   {
+      return groups;
+   }
+   
+   public void setGroups(Set<MemberRole> groups)
+   {
+      this.groups = groups;
+   }   
+   
+   @RoleConditional
+   public boolean isConditional()
+   {
+      return conditional;
+   }
+   
+   public void setConditional(boolean conditional)
+   {
+      this.conditional = conditional;
+   }
+}

Added: examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/PictureAction.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/PictureAction.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/PictureAction.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,41 @@
+package org.jboss.seam.example.seamspace;
+
+import static org.jboss.seam.ScopeType.CONVERSATION;
+
+import javax.persistence.EntityManager;
+
+import org.jboss.seam.annotations.Begin;
+import org.jboss.seam.annotations.In;
+import org.jboss.seam.annotations.Name;
+import org.jboss.seam.annotations.Scope;
+import org.jboss.seam.core.Conversation;
+
+ at Scope(CONVERSATION)
+ at Name("pictureAction")
+public class PictureAction
+{
+   private MemberImage memberImage;
+   
+   @In(required = false)
+   private Member authenticatedMember;
+   
+   @In EntityManager entityManager;
+   
+   @Begin
+   public void uploadPicture()
+   {
+      memberImage = new MemberImage();
+   }
+   
+   public void savePicture()
+   {
+      memberImage.setMember(entityManager.find(Member.class, authenticatedMember.getMemberId()));
+      entityManager.persist(memberImage);
+      Conversation.instance().end();
+   }
+
+   public MemberImage getMemberImage()
+   {
+      return memberImage;
+   }
+}

Added: examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/PictureSearch.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/PictureSearch.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/PictureSearch.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,64 @@
+package org.jboss.seam.example.seamspace;
+
+import static org.jboss.seam.ScopeType.EVENT;
+
+import java.io.Serializable;
+import java.util.List;
+
+import javax.persistence.EntityManager;
+
+import org.jboss.seam.annotations.In;
+import org.jboss.seam.annotations.Name;
+import org.jboss.seam.annotations.Out;
+import org.jboss.seam.annotations.Scope;
+import org.jboss.seam.annotations.security.Delete;
+import org.jboss.seam.annotations.web.RequestParameter;
+import org.jboss.seam.security.Identity;
+
+ at Name("pictureSearch")
+ at Scope(EVENT)
+public class PictureSearch implements Serializable
+{
+   private static final long serialVersionUID = -1868188969326866331L;
+   
+   private String memberName;
+   
+   @In
+   private EntityManager entityManager;
+   
+   @Out(required = false)
+   private List<MemberImage> memberImages;
+   
+   @RequestParameter
+   private Integer imageId;
+   
+   public String getMemberName()
+   {
+      return memberName;
+   }
+
+   public void setMemberName(String memberName)
+   {
+      this.memberName = memberName;
+   }
+   
+   public void delete(@Delete MemberImage image)
+   {
+      entityManager.remove(image);
+   }
+   
+   public MemberImage lookupImage()
+   {
+      return entityManager.find(MemberImage.class, imageId);
+   }
+   
+   @SuppressWarnings("unchecked")
+   public void loadMemberPictures()
+   {
+      memberImages = (List<MemberImage>) entityManager.createQuery(
+            "select i from MemberImage i where i.member.memberName = :name and not i = i.member.picture")
+            .setParameter("name", memberName)
+            .getResultList();      
+      Identity.instance().filterByPermission(memberImages, "view");
+   }
+}

Added: examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/ProfileAction.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/ProfileAction.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/ProfileAction.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,132 @@
+package org.jboss.seam.example.seamspace;
+
+import static org.jboss.seam.ScopeType.CONVERSATION;
+import static org.jboss.seam.ScopeType.EVENT;
+
+import java.util.List;
+import java.util.Random;
+
+import javax.ejb.Remove;
+import javax.persistence.EntityManager;
+import javax.persistence.NoResultException;
+
+import org.jboss.seam.annotations.Destroy;
+import org.jboss.seam.annotations.Factory;
+import org.jboss.seam.annotations.In;
+import org.jboss.seam.annotations.Name;
+import org.jboss.seam.annotations.Out;
+import org.jboss.seam.annotations.web.RequestParameter;
+import org.jboss.seam.annotations.Scope;
+
+ at Name("profile")
+ at Scope(EVENT)
+public class ProfileAction
+{
+   @RequestParameter
+   private String name;
+
+   @In(required = false) @Out(required = false, scope = CONVERSATION)
+   private Member selectedMember;
+   
+   @In(required = false)
+   private Member authenticatedMember;
+   
+   @Out(required = false)
+   List<Member> newMembers;
+   
+   @Out(required = false)
+   List<MemberBlog> memberBlogs;   
+   
+   @In
+   private EntityManager entityManager;
+
+   @Factory("selectedMember")
+   public void display()
+   {      
+      if (name == null && authenticatedMember != null)
+      {
+         selectedMember = (Member) entityManager.find(Member.class, 
+               authenticatedMember.getMemberId());
+      }
+      else if (name != null)
+      {
+         try
+         {
+            selectedMember = (Member) entityManager.createQuery(
+            "from Member where memberName = :memberName")
+            .setParameter("memberName", name)
+            .getSingleResult(); 
+         }
+         catch (NoResultException ex) { }
+      }
+   }
+   
+   /**
+    * Returns the 5 latest blog entries for a member
+    */
+   @SuppressWarnings("unchecked")
+   public List<MemberBlog> getLatestBlogs()
+   {
+      return entityManager.createQuery(
+           "from MemberBlog b where b.member = :member order by b.entryDate desc")
+           .setParameter("member", selectedMember)
+           .setMaxResults(5)
+           .getResultList();
+   }
+
+   /**
+    * Used to read all blog entries for a member
+    */
+   @SuppressWarnings("unchecked")
+   @Factory("memberBlogs")
+   public void getMemberBlogs()
+   {
+      if (name == null && authenticatedMember != null)
+      {
+         name = authenticatedMember.getMemberName();
+      }      
+      
+      memberBlogs = entityManager.createQuery(
+            "from MemberBlog b where b.member.memberName = :memberName order by b.entryDate desc")
+            .setParameter("memberName", name)
+            .getResultList();
+   }   
+   
+   @SuppressWarnings("unchecked")
+   @Factory("newMembers")
+   public void newMembers()
+   {
+      newMembers = entityManager.createQuery(
+            "from Member order by memberSince desc")
+            .setMaxResults(10)
+            .getResultList();
+      
+      // Randomly select 3 of the latest 10 members
+      Random rnd = new Random(System.currentTimeMillis());
+      while (newMembers.size() > 3)
+      {
+         newMembers.remove(rnd.nextInt(newMembers.size()));
+      }
+   }
+   
+   @SuppressWarnings("unchecked")
+   public List<Member> getFriends()
+   {
+      return entityManager.createQuery(
+            "select f.friend from MemberFriend f where f.member = :member and authorized = true")
+            .setParameter("member", selectedMember)
+            .getResultList();
+   }
+   
+   @SuppressWarnings("unchecked")
+   public List<FriendComment> getFriendComments()
+   {
+      return entityManager.createQuery(
+            "from FriendComment c where c.member = :member order by commentDate desc")
+            .setParameter("member", selectedMember)
+            .getResultList();
+   }
+   
+   @Remove @Destroy
+   public void destroy() { }   
+}

Added: examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/RegisterAction.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/RegisterAction.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/java/org/jboss/seam/example/seamspace/RegisterAction.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,203 @@
+package org.jboss.seam.example.seamspace;
+
+import static org.jboss.seam.ScopeType.CONVERSATION;
+
+import java.util.Date;
+
+import javax.ejb.Remove;
+import javax.persistence.EntityManager;
+
+import org.jboss.seam.annotations.Begin;
+import org.jboss.seam.annotations.Destroy;
+import org.jboss.seam.annotations.End;
+import org.jboss.seam.annotations.In;
+import org.jboss.seam.annotations.Name;
+import org.jboss.seam.annotations.Observer;
+import org.jboss.seam.annotations.Scope;
+import org.jboss.seam.faces.FacesMessages;
+import org.jboss.seam.security.Identity;
+import org.jboss.seam.security.RunAsOperation;
+import org.jboss.seam.security.management.IdentityManager;
+import org.jboss.seam.security.management.JpaIdentityStore;
+
+ at Scope(CONVERSATION)
+ at Name("register")
+public class RegisterAction
+{
+   private Member member;
+   
+   @In
+   private EntityManager entityManager;
+   
+   @In
+   private Identity identity;
+   
+   @In
+   private IdentityManager identityManager;
+      
+   private MemberAccount newAccount;
+   
+   private String username;   
+   
+   /**
+    * Password confirmation
+    */
+   private String password;
+   private String confirm;   
+   
+   private String gender;
+   
+   private byte[] picture;
+   private String pictureContentType;
+   
+   private boolean verified;
+
+   @Begin
+   public void start()
+   {
+      member = new Member();
+   }
+   
+   public void next()
+   {
+      member.setGender(Member.Gender.valueOf(gender.toLowerCase()));
+      
+      verified = (confirm != null && confirm.equals(password));
+      
+      if (!verified)
+      {
+         FacesMessages.instance().addToControl("confirmPassword", "Passwords do not match");
+      }           
+   }
+   
+   @Observer(JpaIdentityStore.EVENT_USER_CREATED)
+   public void accountCreated(MemberAccount account)
+   {
+      // The user *may* have been created from the user manager screen. In that
+      // case, create a dummy Member record just for the purpose of demonstrating the
+      // identity management API
+      if (member == null)
+      {
+         member = new Member();
+         member.setMemberName(account.getUsername());
+         member.setGender(Member.Gender.male);
+         member.setFirstName("John");
+         member.setLastName("Doe");
+         member.setEmail(account.getUsername() + "@nowhere.com");
+         member.setDob(new Date());
+         member.setMemberSince(new Date());
+         entityManager.persist(member);
+      }
+      
+      account.setMember(member);
+      this.newAccount = account;
+   }
+
+   @End
+   public void uploadPicture() 
+   {  
+      member.setMemberSince(new Date());      
+      entityManager.persist(member);      
+      
+      new RunAsOperation() {
+         public void execute() {
+            identityManager.createUser(username, password);
+            identityManager.grantRole(username, "user");            
+         }         
+      }.addRole("admin")
+       .run();
+            
+      newAccount.setMember(member);
+      newAccount = entityManager.merge(newAccount);
+
+      if (picture != null && picture.length > 0)
+      {
+         MemberImage img = new MemberImage();
+         img.setData(picture);
+         img.setMember(member);
+         img.setContentType(pictureContentType);
+         entityManager.persist(img);
+         member.setPicture(img);
+         
+         member = entityManager.merge(member);
+      }
+      
+      // Login the user
+      identity.getCredentials().setUsername(username);
+      identity.getCredentials().setPassword(password);
+      identity.login();
+   }
+   
+   public Member getMember()
+   {
+      return member;
+   }
+   
+   public String getUsername()
+   {
+      return username;
+   }
+   
+   public void setUsername(String username)
+   {
+      this.username = username;
+   }
+   
+   public String getPassword()
+   {
+      return password;
+   }
+   
+   public void setPassword(String password)
+   {
+      this.password = password;
+   }
+   
+   public String getConfirm()
+   {
+      return confirm;
+   }
+   
+   public void setConfirm(String confirm)
+   {
+      this.confirm = confirm;
+   }
+   
+   public String getGender()
+   {
+      return gender;
+   }
+   
+   public void setGender(String gender)
+   {
+      this.gender = gender;
+   }
+   
+   public void setPicture(byte[] picture)
+   {
+      this.picture = picture;
+   }
+   
+   public byte[] getPicture()
+   {
+      return picture;
+   }
+   
+   public String getPictureContentType()
+   {
+      return pictureContentType;  
+   }
+   
+   public void setPictureContentType(String contentType)
+   {
+      this.pictureContentType = contentType;
+   }
+   
+   public boolean isVerified()
+   {
+      return verified;
+   }
+   
+   @Destroy @Remove
+   public void destroy() {}
+}

Added: examples/trunk/seamspace/ejb-jar/src/main/resources/META-INF/beans.xml
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/resources/META-INF/beans.xml	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/resources/META-INF/beans.xml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,8 @@
+<Beans xmlns="urn:java:ee"
+   xmlns:faces="urn:java:org.jboss.seam.faces">
+   <Deploy>
+      <Standard/>
+      <Production/>
+      <faces:Faces/>
+   </Deploy>
+</Beans>

Added: examples/trunk/seamspace/ejb-jar/src/main/resources/META-INF/persistence.xml
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/main/resources/META-INF/persistence.xml	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/main/resources/META-INF/persistence.xml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<persistence xmlns="http://java.sun.com/xml/ns/persistence" 
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" 
+   version="1.0">
+   <persistence-unit name="seamspace">
+      <provider>org.hibernate.ejb.HibernatePersistence</provider>
+      <jta-data-source>seamspaceDatasource</jta-data-source>
+      <properties>
+         <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
+         <property name="hibernate.show_sql" value="true"/>
+         <!-- These are the default for JBoss EJB 3, but not for Hibernate EntityManager -->
+         <property name="hibernate.cache.provider_class" value="org.hibernate.cache.HashtableCacheProvider"/>
+         <property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.JBossTransactionManagerLookup"/>
+      </properties>
+   </persistence-unit>
+</persistence>

Added: examples/trunk/seamspace/ejb-jar/src/test/java/org/jboss/seam/example/seamspace/test/BlogTest.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/test/java/org/jboss/seam/example/seamspace/test/BlogTest.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/test/java/org/jboss/seam/example/seamspace/test/BlogTest.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,144 @@
+package org.jboss.seam.example.seamspace.test;
+
+import org.jboss.seam.mock.SeamTest;
+import org.testng.annotations.Test;
+
+public class BlogTest extends SeamTest
+{   
+   @Test
+   public void testCreateBlog() throws Exception
+   {
+      // Log in first
+      new FacesRequest()
+      {
+         @Override
+         protected void invokeApplication() throws Exception
+         {
+            setValue("#{identity.username}", "demo");
+            setValue("#{identity.password}", "demo");
+            invokeAction("#{identity.login}");
+            assert getValue("#{identity.loggedIn}").equals(true);
+         }
+      }.run();
+      
+      String cid = new FacesRequest()
+      {
+         @Override
+         protected void invokeApplication() throws Exception
+         {         
+            assert invokeAction("#{blog.createEntry}") == null;
+         }         
+      }.run();
+      
+      new FacesRequest("/createBlog.xhtml", cid)
+      {
+         @Override 
+         protected void updateModelValues() throws Exception
+         {
+            setValue("#{selectedBlog.title}", "A new blog entry");
+            setValue("#{selectedBlog.text}", "A very very very long section of text. " + 
+                  "This text should be long enough to simulate a typical blog entry. " +
+                  "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Sed interdum " +
+                  "felis non arcu. Phasellus sodales pharetra dui. Suspendisse felis turpis, " +
+                  "ultricies a, ullamcorper sed, nonummy id, nulla. Ut quis orci. Mauris diam " +
+                  "pede, condimentum et, tempor vitae, facilisis non, sem. Mauris quam ipsum, " +
+                  "laoreet non, ultricies in, aliquet nec, metus. Morbi dui. Vestibulum " +
+                  "ullamcorper, tellus non hendrerit consequat, libero erat laoreet metus, " +
+                  "quis facilisis arcu diam vel orci. Fusce tempor erat eget odio. Aliquam urna " +
+                  "dui, dignissim id, pretium in, congue quis, est. Phasellus nec erat ac arcu " +
+                  "porttitor rhoncus. Pellentesque habitant morbi tristique senectus et netus et " +
+                  "malesuada fames ac turpis egestas. Nulla sed massa ut est sodales ultrices. " +
+                  "Sed vitae nulla eu tellus fringilla sagittis. Nunc convallis, mi at lobortis " +
+                  "rhoncus, neque turpis ullamcorper odio, quis scelerisque est dolor non velit. Integer vulputate.");
+         }
+         
+         @Override
+         protected void invokeApplication() throws Exception
+         {
+            assert invokeAction("#{blog.saveEntry}") == null;
+         }
+         
+      }.run();
+    
+      new FacesRequest()
+      {
+         @Override
+         protected void invokeApplication() throws Exception
+         {
+            invokeAction("#{identity.logout}");
+            assert getValue("#{identity.loggedIn}").equals(false);
+         }
+      }.run();      
+   }
+   
+   //@Test
+   public void testCreateComment() throws Exception
+   {
+      new FacesRequest()
+      {
+         @Override
+         protected void invokeApplication() throws Exception
+         {
+            setValue("#{identity.username}", "demo");
+            setValue("#{identity.password}", "demo");
+            invokeAction("#{identity.login}");
+            assert getValue("#{identity.loggedIn}").equals(true);
+         }
+      }.run();   
+      
+      String cid = new FacesRequest("/comment.xhtml")
+      {
+         @Override
+         protected void beforeRequest()
+         {
+            setParameter("name", "Mr_Smiley");
+            setParameter("blogId", "1");
+         }         
+
+         @Override
+         protected void renderResponse() throws Exception
+         {
+              assert getValue("#{selectedBlog}") != null;
+              assert getValue("#{selectedBlog.blogId}").equals(1);
+         }
+      }.run();
+
+      new FacesRequest("/comment.xhtml", cid)
+      {
+         @Override
+         protected void invokeApplication() throws Exception
+         {
+            assert invokeAction("#{blog.createComment}") == null;
+            
+            assert getValue("#{comment}") != null;
+            assert getValue("#{comment.blog}") != null;
+         }
+      }.run();
+      
+       new FacesRequest("/comment.xhtml", cid)
+       {
+          @Override
+          protected void updateModelValues() throws Exception
+          {
+             setValue("#{comment.comment}", "I totally disagree with your blog entry!");
+          }
+         
+          @Override
+          protected void invokeApplication() throws Exception
+          {
+             assert invokeAction("#{blog.saveComment}") == null;
+          }
+       }.run();
+      
+      new FacesRequest()
+      {
+         @Override
+         protected void invokeApplication() throws Exception
+         {
+            invokeAction("#{identity.logout}");
+            assert getValue("#{identity.loggedIn}").equals(false);
+         }
+      }.run();    
+      
+   }
+}

Added: examples/trunk/seamspace/ejb-jar/src/test/java/org/jboss/seam/example/seamspace/test/RegisterTest.java
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/test/java/org/jboss/seam/example/seamspace/test/RegisterTest.java	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/test/java/org/jboss/seam/example/seamspace/test/RegisterTest.java	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,70 @@
+package org.jboss.seam.example.seamspace.test;
+
+import java.util.Date;
+
+import org.jboss.seam.core.Manager;
+import org.jboss.seam.mock.SeamTest;
+import org.testng.annotations.Test;
+
+public class RegisterTest extends SeamTest
+{
+   @Test
+   public void testRegister() throws Exception
+   {
+      String cid = new FacesRequest() 
+      {                 
+         @Override
+         protected void invokeApplication() throws Exception
+         {
+            assert invokeAction("#{register.start}") == null;
+         }         
+      }.run();      
+
+      new FacesRequest("/register.xhtml", cid)
+      {
+         @Override 
+         protected void updateModelValues() throws Exception
+         {
+            setValue("#{register.member.email}", "shane at test.com");
+            setValue("#{register.member.firstName}", "Shane");
+            setValue("#{register.member.lastName}", "Bryzak");
+            setValue("#{register.member.memberName}", "shane123");
+            setValue("#{register.username}", "sbryzak");
+            setValue("#{register.password}", "secret");
+            setValue("#{register.confirm}", "secret");
+            setValue("#{register.gender}", "Male");
+            setValue("#{register.member.dob}", new Date(107100000000L));                        
+         }
+         
+         @Override
+         protected void invokeApplication() throws Exception
+         {
+            assert invokeAction("#{register.next}") == null;
+         }          
+         
+      }.run();
+      
+      new FacesRequest("/register2.xhtml", cid)
+      {         
+         @Override
+         protected void invokeApplication() throws Exception
+         {
+            assert invokeAction("#{register.uploadPicture}") == null;
+            assert !Manager.instance().isLongRunningConversation();
+         }          
+         
+      }.run();     
+      
+      new FacesRequest()
+      {         
+         @Override
+         protected void invokeApplication() throws Exception
+         {
+            assert getValue("#{identity.loggedIn}").equals(true);
+            assert invokeAction("#{identity.logout}") == null;
+            assert getValue("#{identity.loggedIn}").equals(false);
+         }          
+         
+      }.run();       
+   }
+}

Added: examples/trunk/seamspace/ejb-jar/src/test/java/org/jboss/seam/example/seamspace/test/testng.xml
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/test/java/org/jboss/seam/example/seamspace/test/testng.xml	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/test/java/org/jboss/seam/example/seamspace/test/testng.xml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,17 @@
+<!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
+
+<suite name="SeamSpace" verbose="2" parallel="false">
+
+   <test name="SeamSpace: Register">
+     <classes>
+       <class name="org.jboss.seam.example.seamspace.test.RegisterTest"/>
+     </classes>
+   </test>
+   
+   <test name="SeamSpace: Blog">
+     <classes>
+       <class name="org.jboss.seam.example.seamspace.test.BlogTest"/>
+     </classes>
+   </test>
+	
+</suite>
\ No newline at end of file

Added: examples/trunk/seamspace/ejb-jar/src/test/resources/test-suite.xml
===================================================================
--- examples/trunk/seamspace/ejb-jar/src/test/resources/test-suite.xml	                        (rev 0)
+++ examples/trunk/seamspace/ejb-jar/src/test/resources/test-suite.xml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,10 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
+<suite name="Seam Booking Module Test Suite" verbose="1">
+   <test name="Seam Booking Module - Unit Tests">
+
+
+      <packages>
+         <package name="org.jboss.seam.booking"/>
+      </packages>
+   </test>
+</suite>

Added: examples/trunk/seamspace/pom.xml
===================================================================
--- examples/trunk/seamspace/pom.xml	                        (rev 0)
+++ examples/trunk/seamspace/pom.xml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+   <modelVersion>4.0.0</modelVersion>
+
+   <parent>
+      <groupId>org.jboss.seam.examples</groupId>
+      <artifactId>parent</artifactId>
+      <version>3.0.0-SNAPSHOT</version>
+   </parent>
+
+   <artifactId>seam-booking</artifactId>
+   <packaging>pom</packaging>
+   <name>Seam Booking Example (Java EE 5)</name>
+   <description>The Seam booking example for deployment to a Java EE 5 application server</description>
+
+   <modules>
+      <module>ejb-jar</module>
+      <module>war</module>
+      <module>ear</module>
+   </modules>
+
+   <build>
+      <defaultGoal>package</defaultGoal>
+      <pluginManagement>
+
+         <plugins>
+
+            <!-- version matrix or parent? -->
+            <plugin>
+               <artifactId>maven-compiler-plugin</artifactId>
+               <configuration>
+                  <source>1.5</source>
+                  <target>1.5</target>
+               </configuration>
+            </plugin>
+
+            <!-- version matrix or parent? -->
+            <plugin>
+               <artifactId>maven-ejb-plugin</artifactId>
+               <configuration>
+                  <ejbVersion>3.0</ejbVersion>
+               </configuration>
+            </plugin>
+
+         </plugins>
+      </pluginManagement>
+
+      <plugins>
+
+         <plugin>
+            <groupId>org.twdata.maven</groupId>
+            <artifactId>maven-cli-plugin</artifactId>
+            <version>0.6.3.CR2</version> <!-- TODO move to version-matrix -->
+            <configuration>
+               <!-- userAliases are for cli:execute-phase -->
+               <userAliases>
+                  <explode>package -o -Pexplode</explode>
+                  <deploy>seam-booking-ear package -o org.codehaus.mojo:jboss-maven-plugin:harddeploy</deploy>
+                  <undeploy>seam-booking-ear validate -o -Pundeploy</undeploy>
+                  <unexplode>seam-booking-ear validate -o -Pundeploy</unexplode>
+                  <restart>seam-booking-ear validate -o -Prestart</restart>
+                  <reexplode>package -o -Pundeploy -Pexplode</reexplode>
+                  <!--<redeploy>this requires two distinct steps</redeploy>-->
+                  <profiles>org.apache.maven.plugins:maven-help-plugin:active-profiles -o</profiles>
+                  <pom>org.apache.maven.plugins:maven-help-plugin:effective-pom -o</pom>
+               </userAliases>
+               <!-- commands are for cli:execute -->
+               <commands>
+               </commands>
+            </configuration>
+         </plugin>
+
+      </plugins>
+   </build>
+
+   <properties>
+      <!-- To override jboss.home, set the jboss.home property in an active profile in the Maven 2 settings.xml file -->
+      <jboss.home>${env.JBOSS_HOME}</jboss.home>
+      <jboss.domain>default</jboss.domain>
+   </properties>
+
+   <profiles>
+
+      <profile>
+         <id>explode</id>
+         <properties>
+            <maven.test.skip>true</maven.test.skip>
+         </properties>
+      </profile>
+
+      <profile>
+         <id>undeploy</id>
+      </profile>
+
+      <profile>
+         <id>restart</id>
+      </profile>
+
+   </profiles>
+
+   <dependencyManagement>
+      <dependencies>
+
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>seam-booking-ejb</artifactId>
+            <version>${project.version}</version>
+            <type>ejb</type>
+        </dependency>
+
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>seam-booking-war</artifactId>
+            <version>${project.version}</version>
+            <type>war</type>
+        </dependency>
+
+      </dependencies>
+   </dependencyManagement>
+
+   <dependencies>
+
+      <!--
+      <dependency>
+         <groupId>org.testng</groupId>
+         <artifactId>testng</artifactId>
+         <scope>test</scope>
+         <classifier>jdk15</classifier>
+      </dependency>
+      -->
+
+   </dependencies>
+
+</project>

Added: examples/trunk/seamspace/readme.txt
===================================================================
--- examples/trunk/seamspace/readme.txt	                        (rev 0)
+++ examples/trunk/seamspace/readme.txt	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,205 @@
+Seam Booking Example (Java EE 5)
+================================
+
+This example demonstrates the use of Seam 3 in a Java EE 6 environment (or a
+Java EE 5 environment enhanced with JSR-299 [Web Beans], JSF 2.0 and Bean
+Validation). Contextual state management and dependency injection are handled
+by JSR-299. Transaction and persistence context management is handled by the
+EJB 3 container. Validation of input fields is handled by Bean Validation.
+
+= Prerequisites
+
+Please consult the Web Beans reference documentation for instructions on how to
+deploy the Web Bean implementation to JBoss AS 5. To upgrade the JSF libraries
+to JSF 2.0, go to the Seam jsf-upgrade-tool module and follow the instructions
+contained there in pom.xml. You also need to have Bean Validation installed in 
+the container (or added to the classpath) in order for UI validation to work. 
+Installing Bean Validation is as simple as putting the JARs in the library 
+directory of the application server.
+
+You'll also need the 1.0.0-SNAPSHOT of the Web Beans logger extension. You can
+find it in the Web Beans extensions source tree. Run 'mvn install' in that
+directory to have it installed in your local Maven 2 repository.
+
+= First steps
+
+This example uses a Maven 2 build. To build the EJB and WAR and package them
+inside an EAR, execute the following command:
+
+ mvn
+
+Now you're ready to deploy.
+
+= Pointing Maven at your JBoss AS installation
+
+First, set the JBOSS_HOME environment variable to the location of a JBoss AS 5
+installation and start the server. You can optionally set the jboss.home Maven
+property in an active profile defined in the $HOME/.m2/settings.xml file.
+
+Maven assumes JBoss AS is running on port 8080. (This can be changed in the
+plugin configuration). Once that's setup, you can deploy the application to
+JBoss AS using Maven.
+
+= Deploying an packaged or exploded archive to JBoss AS (the wicked fast way)
+
+Since this build is based on Maven 2, you're probably thinking you'll need to
+take a stroll to the kitchen for coffee while you wait for the build to run.
+Well, you better have your coffee ready because this build is fast...and easy!
+
+The secret? The Maven 2 CLI plugin: http://github.com/mrdon/maven-cli-plugin.
+This plugin gives you a command console to execute maven goals and plugins.
+Even better, it supports aliases, so no more having to type long Maven commands
+with switches and properties. Here's how to get started.
+
+First, rev up the command console:
+
+ mvn cli:execute-phase
+
+You'll be presented with a prompt:
+
+maven2> 
+
+At the prompt, you can type any of the following commands (a description is
+provide for each command, but don't type that of course):
+
+explode   -> deploy as an exploded archive to JBoss AS)
+deploy    -> deploy as a packaged archive to JBoss AS
+undeploy  -> undeploy the exploded or packaged archive from JBoss AS
+restart   -> restart the exploded or packaged archive deployed to JBoss AS
+reexplode -> undeploy then explode
+
+You can prefix any command with "clean" if you want to do a clean build:
+
+maven2> clean explode
+
+Leave the console running and enjoy extremely fast deployments ;)
+
+But fast isn't for all of us.
+
+= Deploying a packaged archive to JBoss AS (the Slowskys' way)
+
+Why work fast when you can work slow? Perhaps your boss is annoying you and you
+want to irritate him with long builds. Or maybe you just fear speed. If that's
+the case, this section is for you (or if you just want to know what all those
+commands are doing above).
+
+Putting JBOSS_HOME aside for the moment, you can deploy the application to
+JBoss AS via JMX by executing this command:
+
+ mvn -o -f seam-booking-ear/pom.xml jboss:deploy
+
+You can undeploy the application via JMX using this command:
+
+ mvn -o -f seam-booking-ear/pom.xml jboss:undeploy
+
+Here's the chained restart command via JMX:
+
+ mvn -o -f seam-booking-ear/pom.xml jboss:undeploy && mvn -o package && mvn -o -f seam-booking-ear/pom.xml jboss:deploy
+
+If you would rather deploy more traditional way by copying the archive directly
+to the deploy directory of the JBoss AS domain, use this command instead:
+
+ mvn -o -f seam-booking-ear/pom.xml jboss:harddeploy
+
+But likely during development, you're only interested in exploded archives.
+
+= Deploying a packaged archive to JBoss AS (the Slowskys' way)
+
+It's much better to use the antrun plugin over the jboss plugin since it's
+smarter about what it copies to the server. The antrun plugin is bound to the
+end of the package goal when the explode profile is active:
+
+ mvn -o package -Pexplode
+
+This profile executes an series of Ant tasks that copy the exploded WAR,
+EJB-JAR, and EAR to the JBoss AS deploy directory.
+
+You can force a restart of the application by activating the restart profile,
+which executes an Ant task bound to the validate phase:
+
+ mvn -o validate -Prestart
+
+You can remove the archive by activating the undeploy profile, which also
+executes an Ant task bound to the validate phase:
+
+ mvn -o validate -Pundeploy
+
+Finally, you can undeploy and explode all in one command using both the
+undeploy and explode profiles with a standard package build:
+
+ mvn -o package -Pundeploy,explode
+
+Note that the -o puts Maven in offline mode so that it doesn't perform time
+consuming update checks. The -o flag is included in the aliases configured for
+the Maven CLI plugin documented above.
+
+= Example highlights
+
+- 3 module Maven 2 reactor project (ejb-jar, war, ear)
+- establishes a standard for Seam 3 examples
+- repeat elements are kept to a minimum in the Maven POM files (DRY)
+- JBoss datasource is deployed with the EAR to simplify packaging
+  (-ds.xml doesn't have to be deployed to JBoss AS separately)
+- supports both a packaged and exploded deployment to JBoss AS
+
+= Known issues
+
+(1) Clicking on logout throws an exception
+    javax.context.ContextNotActiveException: No active contexts for scope type javax.context.RequestScoped
+       at org.jboss.webbeans.ManagerImpl.getContext(ManagerImpl.java:739)
+       at org.jboss.webbeans.bean.proxy.ClientProxyMethodHandler.getProxiedInstance(ClientProxyMethodHandler.java:116)
+       at org.jboss.webbeans.bean.proxy.ClientProxyMethodHandler.invoke(ClientProxyMethodHandler.java:96)
+       at org.jboss.webbeans.conversation.ConversationImpl_$$_javassist_213.isLongRunning(ConversationImpl_$$_javassist_213.java)
+
+(2) Ajax is not working on blur in p:edit form fields (had to disable)
+    error malformedXML
+
+(3) No list of workspaces
+
+(4) Cannot use <f:view> in template or else it will remove the conversation id token from the view root
+
+(5) @AfterTransactionSuccess observer does not work
+
+(6) Can't read properties from Hotel object in history list. Get this exception:
+    javax.inject.IllegalProductException: Cannot return null from a non-dependent producer method
+       at org.jboss.webbeans.bean.AbstractProducerBean.checkReturnValue(AbstractProducerBean.java:209)
+       at org.jboss.webbeans.bean.AbstractProducerBean.create(AbstractProducerBean.java:349)
+       at org.jboss.webbeans.context.AbstractMapContext.get(AbstractMapContext.java:98)
+       at org.jboss.webbeans.bean.proxy.ClientProxyMethodHandler.getProxiedInstance(ClientProxyMethodHandler.java:117)
+       at org.jboss.webbeans.bean.proxy.ClientProxyMethodHandler.invoke(ClientProxyMethodHandler.java:96)
+       at org.jboss.seam.examples.booking.model.Hotel_$$_javassist_227.getAddress(Hotel_$$_javassist_227.java)
+
+= Open questions
+
+- How do I clear a contextual bean from a scope, in particular the session scope? I've had to do workarounds.
+
+- How do I inject an Event object into a stateful component? I get an error that there is a reference to a
+  non-serializable object from a bean declaring a non-passivating scope. I have to use the Manager to fire an event
+  instead.
+
+- Should the parent project be adjacent to ear/ejb-jar/war?
+
+= TODO
+
+- secure pages (likely will use <f:event type="beforeRenderView"/>
+
+- get status messages from default or resource bundle (not just hardcoded defaults)
+
+- demonstrate use of exception handling in JSF 2
+
+- auto-detect which files have @NotNull or @NotEmpty to determine whether to put the * in <p:edit>
+
+- use Cargo plugin to support deployment to other Java EE servers (GlassFish)
+
+- refactor the password/confirm password into a reusable component (needed on
+  registration and change password)
+
+- consider using unpackTypes in the ear plugin (which works incrementally) to replace portions of the antrun logic
+
+- use a resource to define persistence context
+<EntityManager>
+   <PersistenceContext>
+      <unitName>booking</unitName>
+   </PersistenceContext>
+   <booking:BookingDatabase/>
+</EntityManager>

Added: examples/trunk/seamspace/war/pom.xml
===================================================================
--- examples/trunk/seamspace/war/pom.xml	                        (rev 0)
+++ examples/trunk/seamspace/war/pom.xml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+   <modelVersion>4.0.0</modelVersion>
+
+   <parent>
+      <groupId>org.jboss.seam.examples</groupId>
+      <artifactId>seam-space</artifactId>
+      <version>3.0.0-SNAPSHOT</version>
+   </parent>
+
+   <artifactId>seam-space-war</artifactId>
+   <packaging>war</packaging>
+   <name>SeamSpace Security Example (Web module)</name>
+
+   <build>
+      <defaultGoal>package</defaultGoal>
+      <finalName>${project.parent.artifactId}</finalName>
+   </build>
+
+   <dependencies>
+
+      <!--
+      <dependency>
+         <groupId>javax.faces</groupId>
+         <artifactId>jsf-api</artifactId>
+         <scope>provided</scope>
+      </dependency>
+
+      <dependency>
+         <groupId>javax.el</groupId>
+         <artifactId>el-api</artifactId>
+         <scope>provided</scope>
+      </dependency>
+      -->
+
+      <!-- is this necessary? is it for upgrading to EL 1.2? -->
+      <!--
+      <dependency>
+         <groupId>javax.el</groupId>
+         <artifactId>el-ri</artifactId>
+         <scope>runtime</scope>
+         <exclusions>
+            <exclusion>
+               <groupId>javax.el</groupId>
+               <artifactId>el-api</artifactId>
+            </exclusion>
+         </exclusions>
+      </dependency>
+      -->
+
+      <!--
+      <dependency>
+         <groupId>${seam.groupId}</groupId>
+         <artifactId>seam-faces</artifactId>
+      </dependency>
+      -->
+
+      <!-- pulled in by seam-el
+      <dependency>
+         <groupId>org.jboss.el</groupId>
+         <artifactId>jboss-el</artifactId>
+         <scope>runtime</scope>
+         <exclusions>
+            <exclusion>
+               <groupId>javax.el</groupId>
+               <artifactId>el-api</artifactId>
+            </exclusion>
+         </exclusions>
+      </dependency>
+      -->
+      
+   </dependencies>
+
+</project>

Added: examples/trunk/seamspace/war/src/main/webapp/WEB-INF/faces-config.xml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/WEB-INF/faces-config.xml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/WEB-INF/faces-config.xml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<faces-config 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-facesconfig_2_0.xsd"
+   version="2.0">
+   
+
+
+
+
+</faces-config>

Added: examples/trunk/seamspace/war/src/main/webapp/WEB-INF/web.xml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/WEB-INF/web.xml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/WEB-INF/web.xml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app 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"
+   version="2.5">
+
+   <display-name>SeamSpace Security Example</display-name>
+
+   <context-param>
+      <param-name>facelets.DEVELOPMENT</param-name>
+      <param-value>true</param-value>
+   </context-param>
+   
+   <context-param>
+      <param-name>javax.faces.INTERPRET_EMPTY_STRING_SUBMITTED_VALUES_AS_NULL</param-name>
+      <param-value>true</param-value>
+   </context-param>
+
+   <context-param>
+      <param-name>javax.faces.PROJECT_STAGE</param-name>
+      <param-value>Development</param-value>
+   </context-param>
+   
+   <servlet>
+      <servlet-name>Faces Servlet</servlet-name>
+      <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
+      <load-on-startup>1</load-on-startup>
+   </servlet>
+   
+   <servlet-mapping>
+      <servlet-name>Faces Servlet</servlet-name>
+      <url-pattern>*.seam</url-pattern>
+   </servlet-mapping>
+
+   <session-config>
+      <session-timeout>10</session-timeout> 
+   </session-config>   
+
+   <security-constraint>
+      <display-name>Restrict access to XHTML documents</display-name>
+      <web-resource-collection>
+         <web-resource-name>XHTML</web-resource-name>
+         <url-pattern>*.xhtml</url-pattern>
+      </web-resource-collection>
+      <auth-constraint/>
+   </security-constraint>
+   
+</web-app>

Added: examples/trunk/seamspace/war/src/main/webapp/blog.xhtml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/blog.xhtml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/blog.xhtml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,60 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:f="http://java.sun.com/jsf/core"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:s="http://jboss.com/products/seam/taglib">
+
+  <ui:composition template="template.xhtml">
+    <ui:define name="content">
+      
+      <div class="errors"><h:messages globalOnly="true"/></div>       
+
+      <s:div rendered="#{selectedMember == null}">
+        Sorry, but this member does not exist.
+      </s:div>
+    
+      <s:div rendered="#{selectedMember != null}">
+      
+        <s:div id="blogMemberCard">	        
+	        <s:link view="/profile.seam" propagation="none">
+	          #{selectedMember.memberName}<br/>
+              <h:graphicImage value="/content/images?id=#{selectedMember.picture.imageId}&amp;width=90"/>	          
+	        </s:link>
+                           
+          <br style="clear:both"/>          
+        </s:div>
+
+				<s:div id="blog">
+	        <ui:repeat value="#{memberBlogs}" var="memberBlog">
+	          <div class="blogEntry">
+  	          <div class="blogDate">#{memberBlog.formattedEntryDate}</div>
+	            <div class="blogTitle">#{memberBlog.title}</div>
+	            <div class="blogText"><s:formattedText value="#{memberBlog.text}"/></div>
+	            <div class="blogFooter">
+
+	              [<s:link view="/blogentry.seam" propagation="none">
+	                <f:param name="name" value="#{selectedMember.memberName}"/>
+	                <f:param name="blogId" value="#{memberBlog.blogId}"/>
+  	              #{memberBlog.commentCount} Comment#{memberBlog.commentCount != 1 ? "s" : ""}
+  	             </s:link>]
+	              
+	              <s:span rendered="#{s:hasPermission('blog','createComment')}">
+	                [<s:link view="/comment.seam" value="Add Comment" propagation="none">
+	                   <f:param name="name" value="#{selectedMember.memberName}"/>
+	                   <f:param name="blogId" value="#{memberBlog.blogId}"/>
+	                 </s:link>]
+	              </s:span>
+	            </div>
+	          </div>
+	        </ui:repeat>
+	      </s:div>
+                
+      </s:div>  
+      
+      <br class="clear"/>                    
+          
+    </ui:define>
+    
+  </ui:composition>
+</html>

Added: examples/trunk/seamspace/war/src/main/webapp/blogentry.xhtml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/blogentry.xhtml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/blogentry.xhtml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,74 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:f="http://java.sun.com/jsf/core"
+    xmlns:s="http://jboss.com/products/seam/taglib">
+
+  <ui:composition template="template.xhtml">
+    <ui:define name="content">
+      
+      <div class="errors"><h:messages globalOnly="true"/></div>       
+
+      <s:div rendered="#{selectedBlog == null}">
+        Sorry, but this blog entry does not exist.
+      </s:div>
+    
+      <s:div rendered="#{selectedBlog != null}">
+      
+        <s:div id="blogMemberCard">	        
+	        <s:link view="/profile.seam" propagation="none">
+	          #{selectedMember.memberName}<br/>
+              <h:graphicImage value="/content/images?id=#{selectedMember.picture.imageId}&amp;width=90"/>	          
+	        </s:link>
+                           
+          <br style="clear:both"/>          
+        </s:div>
+
+				<s:div id="blog">
+          <div class="blogEntry">
+	          <div class="blogDate">#{selectedBlog.formattedEntryDate}</div>
+            <div class="blogTitle">#{selectedBlog.title}</div>
+            <div class="blogText"><s:formattedText value="#{selectedBlog.text}"/></div>
+            <div class="blogFooter">
+              <s:span rendered="#{s:hasPermission(selectedBlog, 'create')}">              
+                [<s:link action="#{commentAction.createComment}" value="Add Comment"/>]
+              </s:span>
+              [<s:link view="/blog.seam" value="View all blog entries" propagation="none">
+                 <f:param name="name" value="#{selectedMember.memberName}"/>
+               </s:link>]
+            </div>
+          </div>
+                    
+	        <ui:repeat value="#{selectedBlog.sortedComments}" var="comment">
+	          <table class="blogComment">
+	            <tr>
+		            <td class="blogCommentor">					        
+					        <s:link view="/profile.seam" propagation="none">
+					          <f:param name="name" value="#{comment.commentor.memberName}"/>
+					          #{comment.commentor.memberName}<br/>
+					          <h:graphicImage value="/content/images?id=#{comment.commentor.picture.imageId}&amp;width=90"/>
+					        </s:link>
+		            </td>
+		            
+		            <td class="blogCommentText">
+		              <p><s:formattedText value="#{comment.comment}"/></p>
+		              <p>Posted by 					          
+					          <s:link view="/profile.seam" value="#{comment.commentor.memberName}" propagation="none">
+					            <f:param name="name" value="#{comment.commentor.memberName}"/>
+					          </s:link> on #{comment.formattedCommentDate}
+  					      </p>
+		            </td>	            
+	            </tr>
+	          </table>
+	        </ui:repeat>          
+	      </s:div>
+	                      
+      </s:div>                
+          
+      <br class="clear"/>          
+          
+    </ui:define>
+    
+  </ui:composition>
+</html>

Added: examples/trunk/seamspace/war/src/main/webapp/comment.xhtml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/comment.xhtml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/comment.xhtml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,89 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:f="http://java.sun.com/jsf/core"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:s="http://jboss.com/products/seam/taglib">
+
+  <ui:composition template="template.xhtml">
+    <ui:define name="content">
+
+      <div class="errors"><h:messages globalOnly="true"/></div>
+
+      <s:div rendered="#{commentAction.comment == null}">
+        Could not create comment.
+      </s:div>
+
+      <s:div rendered="#{selectedMember != null}">
+
+        <s:div id="blogMemberCard">
+	        <s:link view="/profile.seam" propagation="none">
+	          	#{selectedMember.memberName}<br/>
+            <h:graphicImage value="/content/images?id=#{selectedMember.picture.imageId}&amp;width=90"/>	          	
+	        </s:link>
+
+          <br style="clear:both"/>
+        </s:div>
+
+				<s:div id="blog">
+          <div class="blogEntry">
+	          <div class="blogDate">#{selectedBlog.formattedEntryDate}</div>
+            <div class="blogTitle">#{selectedBlog.title}</div>
+            <div class="blogText"><s:formattedText value="#{selectedBlog.text}"/></div>
+          </div>
+
+          <s:div rendered="#{commentAction.comment.comment != null}">
+            Preview:
+	          <table class="blogComment">
+	            <tr>
+		            <td class="blogCommentText">
+		              <p><s:formattedText value="#{commentAction.comment.comment}"/></p>
+		            </td>
+	            </tr>
+	          </table>
+          </s:div>
+
+          <div class="commentEntry">
+            <h:form>
+
+              <h:outputLabel for="comment">Please type your comment</h:outputLabel><br/>
+              <h:inputTextarea id="comment" value="#{commentAction.comment.comment}"/><br/>
+
+              <div class="buttons">
+                <h:commandButton action="#{commentAction.saveComment}" value="Add comment" styleClass="action"/>
+                <h:commandButton value="Preview" styleClass="action"/>
+              </div>
+
+              <br class="clear"/>
+            </h:form>
+          </div>
+
+          <div>
+            <b>Seam Text Quick Reference</b><br/>
+            <pre><code>
+*bold* /italic/ |monospace| -strikethrough- ^super^ _underline_
+
++Big Heading
+Headings must be followed by text
+
+++Smaller heading
+Paragraphs are ended with a blank line.
+
+#ordered list item
+
+=unordered list item
+
+"quoted text"
+            </code></pre>
+          </div>
+
+        </s:div>
+
+      </s:div>
+
+      <br class="clear"/>
+
+    </ui:define>
+
+  </ui:composition>
+</html>

Added: examples/trunk/seamspace/war/src/main/webapp/createBlog.xhtml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/createBlog.xhtml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/createBlog.xhtml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,84 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:f="http://java.sun.com/jsf/core"
+    xmlns:s="http://jboss.com/products/seam/taglib">
+
+  <ui:composition template="template.xhtml">
+    <ui:define name="content">
+      
+      <div class="errors"><h:messages globalOnly="true"/></div>       
+
+      <s:div rendered="#{selectedBlog == null}">
+        Sorry, but this blog entry does not exist.
+      </s:div>
+    
+      <s:div rendered="#{selectedBlog != null}">
+      
+        <s:div id="blogMemberCard">	        
+	        <s:link view="/profile.seam" propagation="none">
+	          #{selectedMember.memberName}<br/>
+            <h:graphicImage value="/content/images?id=#{selectedMember.picture.imageId}&amp;width=90"/>	          
+	        </s:link>
+                           
+          <br style="clear:both"/>          
+        </s:div>
+
+				<s:div id="blog">
+          <s:div rendered="#{selectedBlog.text != null}" styleClass="blogEntry">
+            Preview:
+            <p><s:formattedText value="#{selectedBlog.text}"/></p>
+          </s:div>				
+				
+          <div class="blogEntry">
+            <h:form id="newBlog">
+              
+              <div class="formRow">
+                <h:outputLabel for="title">Please type a title for your blog entry</h:outputLabel><br/>
+                <h:inputText id="title" value="#{selectedBlog.title}" styleClass="title"/>
+              </div>
+              
+              <div class="formRow">
+                <h:outputLabel for="text">Type your blog entry here</h:outputLabel><br/>
+                <h:inputTextarea id="text" value="#{selectedBlog.text}"/>
+              </div>
+
+              <div class="buttons">
+                <h:commandButton id="submit" action="#{blog.saveEntry}" value="Add entry" styleClass="action"/>            
+                <h:commandButton id="preview" value="Preview" styleClass="action"/>                            
+              </div>
+              
+              <br class="clear"/>
+            </h:form>                      
+          </div>
+                	      
+          <div>
+            <b>Seam Text Quick Reference</b><br/>
+            <pre><code>
+*bold* /italic/ |monospace| -strikethrough- ^super^ _underline_
+
++Big Heading
+Headings must be followed by text
+
+++Smaller heading
+Paragraphs are ended with a blank line.
+
+#ordered list item
+
+=unordered list item
+
+"quoted text"
+            </code></pre>
+          </div>
+                    
+	      </s:div>      
+	                      
+      </s:div>                
+          
+      <br class="clear"/>          
+          
+    </ui:define>
+    
+  </ui:composition>
+</html>

Added: examples/trunk/seamspace/war/src/main/webapp/favicon.ico
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/favicon.ico
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/friendcomment.xhtml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/friendcomment.xhtml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/friendcomment.xhtml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,84 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:f="http://java.sun.com/jsf/core"
+    xmlns:s="http://jboss.com/products/seam/taglib">
+
+  <ui:composition template="template.xhtml">
+    <ui:define name="content">
+      
+      <div class="errors"><h:messages globalOnly="true"/></div>       
+
+      <s:div rendered="#{friendComment == null}">
+        Could not create comment.
+      </s:div>
+    
+      <s:div rendered="#{friendComment != null}">
+      
+        <s:div id="blogMemberCard">
+          <h:outputLink value="profile.seam?name=#{friendComment.member.memberName}">
+	          #{friendComment.member.memberName}<br/>
+            <h:graphicImage value="/content/images?id=#{friendComment.member.picture.imageId}&amp;width=90"/>
+	        </h:outputLink>
+                           
+          <br style="clear:both"/>          
+        </s:div>
+
+				<s:div id="blog">
+          
+          <s:div rendered="#{friendComment.comment != null}">
+            Preview:
+	          <table class="blogComment">
+	            <tr>		            
+		            <td class="blogCommentText">
+		              <p><s:formattedText value="#{friendComment.comment}"/></p>
+		            </td>	            
+	            </tr>
+	          </table>          
+          </s:div>
+          
+          <div class="commentEntry">
+            <h:form>
+              
+              <h:outputLabel for="comment">Please type your comment for #{friendComment.member.memberName}</h:outputLabel><br/>
+              <h:inputTextarea id="comment" value="#{friendComment.comment}"/><br/>
+
+              <div class="buttons">
+                <h:commandButton action="#{friendAction.saveComment}" value="Add comment" styleClass="action"/>            
+                <h:commandButton value="Preview" styleClass="action"/>                            
+              </div>
+              
+              <br class="clear"/>
+            </h:form>
+          </div>
+          
+          <div>
+            <b>Seam Text Quick Reference</b><br/>
+            <pre><code>
+*bold* /italic/ |monospace| -strikethrough- ^super^ _underline_
+
++Big Heading
+Headings must be followed by text
+
+++Smaller heading
+Paragraphs are ended with a blank line.
+
+#ordered list item
+
+=unordered list item
+
+"quoted text"
+            </code></pre>
+          </div>
+          
+        </s:div>
+                
+      </s:div>                
+      
+      <br class="clear"/>
+          
+    </ui:define>
+    
+  </ui:composition>
+</html>

Added: examples/trunk/seamspace/war/src/main/webapp/friendrequest.xhtml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/friendrequest.xhtml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/friendrequest.xhtml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,56 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:f="http://java.sun.com/jsf/core"
+    xmlns:s="http://jboss.com/products/seam/taglib">
+
+  <ui:composition template="template.xhtml">
+    <ui:define name="content">
+
+      <div class="errors"><h:messages globalOnly="true"/></div>
+
+      <s:div rendered="#{friendRequest == null}">
+        Could not create friend request.
+      </s:div>
+
+      <s:div rendered="#{friendRequest != null}">
+
+        <s:div id="blogMemberCard">	        
+	        <s:link view="/profile.seam" value="#{friendRequest.member.memberName}" propagation="none">
+            <f:param name="name" value="#{friendRequest.member.memberName}"/>	        
+              <h:graphicImage value="/content/images?id=#{friendRequest.member.picture.imageId}&amp;width=90"/>
+	        </s:link>
+
+          <br style="clear:both"/>
+        </s:div>
+
+        <div class="friendRequest">
+          <h1>Send a friend request</h1>
+          
+          <p>
+            Use this form to request that <b>#{friendRequest.member.memberName}</b> adds you to 
+            #{friendRequest.member.gender.possessive} friends list.
+          </p>
+        
+          <h:form>
+
+            <h:outputLabel for="introduction">Please type an introduction message</h:outputLabel><br/>
+            <h:inputTextarea id="introduction" value="#{friendRequest.introduction}"/><br/>
+
+            <div class="buttons">
+              <h:commandButton action="#{friendAction.saveRequest}" value="Send request" styleClass="action"/>
+            </div>
+
+            <br class="clear"/>
+          </h:form>
+        </div>
+
+      </s:div>
+
+      <br class="clear"/>
+
+    </ui:define>
+
+  </ui:composition>
+</html>

Added: examples/trunk/seamspace/war/src/main/webapp/hashgen.xhtml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/hashgen.xhtml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/hashgen.xhtml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,49 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:f="http://java.sun.com/jsf/core"
+    xmlns:s="http://jboss.com/products/seam/taglib">
+
+  <ui:composition template="template.xhtml">
+    <ui:define name="content">
+      <h1>Password Hash Generator</h1>
+      
+      <p>
+        This page uses the methods in JpaIdentityStore to generate password hash values that you can
+        use in your own application's import.sql to create default accounts for your application. 
+      </p>
+      
+      <p>
+        Please note that you must have a property annotated @PasswordSalt for these hash values to work!
+      </p>
+      
+      <h:form>    
+        
+        <div class="formRow">
+          <h:outputLabel for="password">Enter a password</h:outputLabel>
+          <h:inputText id="password" value="#{hashgenerator.password}" required="true" styleClass="wide"/>
+          <div class="validationError"><h:message for="password"/></div>
+        </div>         
+
+        <div class="formRow">
+          <h:outputLabel for="salt">Password salt</h:outputLabel>
+          <h:inputText id="salt" value="#{hashgenerator.passwordSalt}" required="false" styleClass="wide"/>
+          <span>(Leave blank to generate a random salt)</span>
+          <div class="validationError"><h:message for="salt"/></div>
+        </div>         
+        
+        <h:commandButton action="#{hashgenerator.generate}" value="Generate hash"/>                    
+      
+      </h:form>
+      
+      <h2>Results</h2>
+      
+      <div>Generated hash (hex encoded): <pre>#{hashgenerator.passwordHash}</pre></div>
+      
+      <div>Example SQL:<br/><textarea style="width:640px;height:50px">#{hashgenerator.sql}</textarea></div>
+             
+    </ui:define>
+    
+  </ui:composition>
+</html>

Added: examples/trunk/seamspace/war/src/main/webapp/home.xhtml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/home.xhtml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/home.xhtml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,93 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:f="http://java.sun.com/jsf/core"
+    xmlns:s="http://jboss.com/products/seam/taglib">
+
+  <ui:composition template="template.xhtml">
+    <ui:define name="content">
+      <div id="contentMain">
+        <h1>Welcome to seamspace!</h1>
+        
+        <p>
+          This example project is an imitation of a popular social networking site, and has
+          been put together to demonstrate the various features of the Seam Security API.
+        </p>
+        
+        <p><b>New!</b> You can now use the <s:link view="/hashgen.xhtml" value="Password Hash Generator"/> 
+           page to generate password hashes for your own application.
+        </p>
+        
+      </div>
+
+      <div id="contentDivider">
+      </div>
+
+      <div id="contentSide">
+        <div class="advertising"></div>
+               
+        <h:form id="loginForm" rendered="#{not identity.loggedIn}">
+          <div class="memberLogin">
+            <div class="loginHeader">Member Login</div>
+            
+            <h:messages id="messages" globalOnly="true"/>
+            
+            <div class="loginRow">
+              <h:outputLabel for="name" value="Member name" styleClass="loginLabel"/>
+              <h:inputText id="name" value="#{credentials.username}"/>
+            </div>
+            
+            <div class="validationMsg">
+              <h:message for="name"/>
+            </div>
+            
+            <div class="loginRow">
+              <h:outputLabel for="password" value="Password" styleClass="loginLabel"/>
+              <h:inputSecret id="password" value="#{credentials.password}" redisplay="true"/>
+            </div>
+            
+            <div class="validationMsg">
+              <h:message for="password"/>
+            </div>          
+            
+            <div class="loginRow">
+              <h:outputLabel for="rememberMe" value="Remember me" styleClass="loginLabel"/>
+              <h:selectBooleanCheckbox id="rememberMe" value="#{identity.rememberMe}"/>
+            </div>
+
+            <div class="buttons">
+              <h:commandButton id="login" value="LOGIN" action="#{identity.login}" styleClass="loginButton"/>
+              <h:commandButton id="register" value="SIGN UP!" action="#{register.start}" styleClass="registerButton"/>
+            </div>
+
+            <br class="clear"/>
+          
+          </div>
+          
+          <span>Tip: You can log in using <b>demo/demo</b> as the username/password</span>
+        </h:form>
+        
+        <div class="newMembers">
+          <div class="newMembersHeader">Cool new members</div>
+                    
+          <ui:repeat value="#{newMembers}" var="newMember">
+            <div class="newMember">
+
+              <s:link view="/profile.seam" propagation="none">
+                <f:param name="name" value="#{newMember.memberName}"/>
+                #{newMember.memberName}<br/>
+                <h:graphicImage value="/content/images?id=#{newMember.picture.imageId}&amp;width=90"/>
+              </s:link>
+              
+            </div>
+          </ui:repeat>          
+          
+          <br class="clear"/>
+        </div>
+      </div>
+     
+    </ui:define>
+    
+  </ui:composition>
+</html>

Added: examples/trunk/seamspace/war/src/main/webapp/imagepermissiondetail.page.xml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/imagepermissiondetail.page.xml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/imagepermissiondetail.page.xml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,9 @@
+<page xmlns="http://jboss.com/products/seam/pages"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.1.xsd">
+    <navigation from-action="#{imagePermission.applyPermissions}">
+      <rule if-outcome="success">
+        <redirect view-id="/imagepermissions.xhtml"/>
+      </rule>
+    </navigation>
+</page>

Added: examples/trunk/seamspace/war/src/main/webapp/imagepermissiondetail.xhtml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/imagepermissiondetail.xhtml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/imagepermissiondetail.xhtml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,89 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:f="http://java.sun.com/jsf/core"
+    xmlns:rich="http://richfaces.org/rich"       
+    xmlns:s="http://jboss.com/products/seam/taglib">
+
+  <ui:composition template="template.xhtml">
+  
+    <ui:define name="head">
+      <link href="style/security.css" rel="stylesheet" type="text/css"/>
+    </ui:define>  
+   
+    <ui:define name="content">
+        
+      <div id="contentMain">
+
+  	    <h2>Permission Details</h2>  
+  	    
+  	    <hr/>
+  	    
+  	    <h:messages globalOnly="true"/>
+
+  	    <h:form>
+          <s:fragment rendered="#{imagePermission.recipient eq null}">  	    
+      	    <h3>Recipients</h3>
+      	    
+      	    <p>
+      	      Select the roles and/or friends that you wish to grant permissions to, for the following image
+      	      (Ctrl-click to select multiple):
+      	    </p>
+      	    
+      	    <br class="clear"/>
+      	    
+            <div class="thumbnail">
+              <h:graphicImage value="/content/images?id=#{permissionSearch.target.imageId}&amp;width=90"/>
+            </div>        	      	    
+            
+            <br class="clear"/>
+      	    
+      	    <div class="formRow">
+      	      <h:outputLabel for="roles" value="Roles" styleClass="formLabel"/>
+      	      <div class="selectMany">
+      	        <h:selectManyListbox id="roles" value="#{imagePermission.selectedRoles}" styleClass="roles" size="4">
+      	          <s:selectItems value="#{identityManager.listRoles()}" var="role" label="#{role}"/>
+      	        </h:selectManyListbox>
+      	      </div>
+      	      <div class="validationError"><h:message for="actions"/></div>
+      	    </div>
+      	    
+      	    <div class="formRow">
+      	      <h:outputLabel for="friends" value="Friends" styleClass="formLabel"/>
+      	      <div class="selectMany">
+      	        <h:selectManyListbox id="friends" value="#{imagePermission.selectedFriends}" styleClass="roles" size="6">
+      	          <s:selectItems value="#{imagePermission.availableFriends}" var="friend" label="#{friend.memberName}"/>
+      	          <s:convertEntity />
+      	        </h:selectManyListbox>
+      	      </div>
+      	      <div class="validationError"><h:message for="friends"/></div>
+      	    </div>
+      	    
+      	  </s:fragment>
+                            
+          <div class="formRow">            
+            <h:outputLabel for="actions" value="Actions allowed" styleClass="formLabel"/>
+            <div class="selectMany">
+              <h:selectManyCheckbox id="actions" value="#{imagePermission.selectedActions}" layout="pageDirection" styleClass="roles">
+                <s:selectItems value="#{permissionManager.listAvailableActions(imagePermission.target)}" var="action" label="#{action}"/>
+              </h:selectManyCheckbox>
+            </div>
+            <div class="validationError"><h:message for="actions"/></div>
+          </div>
+                                                
+          <div class="formButtons">
+            <h:commandButton value="Save" action="#{imagePermission.applyPermissions}" styleClass="formButton"/>
+            <s:button view="/imagepermissions.xhtml" value="Cancel" propagation="end" styleClass="formButton"/>
+          </div>
+    
+          <br class="clear"/>
+  	    
+  	    </h:form>
+
+	    </div>
+	    
+    </ui:define>
+    
+  </ui:composition>
+</html>

Added: examples/trunk/seamspace/war/src/main/webapp/imagepermissions.page.xml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/imagepermissions.page.xml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/imagepermissions.page.xml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,11 @@
+<page xmlns="http://jboss.com/products/seam/pages"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.1.xsd"
+    action="#{permissionSearch.refresh}">
+    <navigation from-action="#{imagePermission.createPermission}">
+        <redirect view-id="/imagepermissiondetail.xhtml"/>
+    </navigation>
+    <navigation from-action="#{imagePermission.editPermission}">
+        <redirect view-id="/imagepermissiondetail.xhtml"/>
+    </navigation>
+</page>

Added: examples/trunk/seamspace/war/src/main/webapp/imagepermissions.xhtml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/imagepermissions.xhtml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/imagepermissions.xhtml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,82 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:f="http://java.sun.com/jsf/core"
+    xmlns:s="http://jboss.com/products/seam/taglib">
+
+  <ui:composition template="template.xhtml">
+
+    <ui:define name="head">
+      <link href="style/security.css" rel="stylesheet" type="text/css"/>
+    </ui:define>
+
+    <ui:define name="content">
+
+      <script type="text/javascript">
+        function confirmRevoke()
+        {
+          return confirm("Are you sure you wish to revoke this permission? This action cannot be undone.");
+        }
+      </script>
+
+      <div id="contentMain">
+
+  	    <h2>Image Permissions</h2>
+
+  	    <div style="float: left">
+  	      <h3>Managing permissions for image:</h3>
+  	    </div>
+
+        <br class="clear"/>
+
+        <div class="thumbnail">
+          <h:graphicImage value="/content/images?id=#{permissionSearch.target.imageId}&amp;width=90"/>
+        </div>
+
+    	  <br class="clear"/>
+
+        <s:button action="#{imagePermission.createPermission}"
+          styleClass="newpermission"
+          rendered="#{s:hasPermission(permissionSearch.target, 'seam.grant-permission')}"/>
+
+        <h:dataTable
+            id="threads"
+            value="#{recipients}"
+            var="recipient"
+            styleClass="security"
+            cellspacing="0"
+            headerClass="header"
+            rowClasses="odd,even"
+            columnClasses=",,action">
+          <h:column width="auto">
+            <f:facet name="header">
+              Recipient
+            </f:facet>
+            #{recipient.name}
+          </h:column>
+          <h:column width="auto">
+            <f:facet name="header">
+              Actions Allowed
+            </f:facet>
+            #{permissionSearch.getActions(recipient)}
+          </h:column>
+          <h:column width="auto">
+            <f:facet name="header">
+              Action
+            </f:facet>
+            <s:link value="Edit" action="#{imagePermission.editPermission}"
+                    rendered="#{s:hasPermission(permissionSearch.target, 'seam.revoke-permission')}"/>
+            <span> | </span>                    
+            <s:link value="Revoke All" action="#{permissionSearch.revokeSelected}"
+                    rendered="#{s:hasPermission(permissionSearch.target, 'seam.revoke-permission')}"
+                    onclick="return confirmRevoke()"/>
+          </h:column>
+  	    </h:dataTable>
+
+	    </div>
+
+    </ui:define>
+
+  </ui:composition>
+</html>

Added: examples/trunk/seamspace/war/src/main/webapp/images/bg_button.png
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/images/bg_button.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/images/checkmark.png
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/images/checkmark.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/images/cross.png
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/images/cross.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/images/ellipsis.png
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/images/ellipsis.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/images/no_image.png
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/images/no_image.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/index.html
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/index.html	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/index.html	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,5 @@
+<html>
+<head>
+  <meta http-equiv="Refresh" content="0; URL=home.seam">
+</head>
+</html>
\ No newline at end of file

Added: examples/trunk/seamspace/war/src/main/webapp/lightbox/builder.js
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/lightbox/builder.js	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/lightbox/builder.js	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,136 @@
+// script.aculo.us builder.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+var Builder = {
+  NODEMAP: {
+    AREA: 'map',
+    CAPTION: 'table',
+    COL: 'table',
+    COLGROUP: 'table',
+    LEGEND: 'fieldset',
+    OPTGROUP: 'select',
+    OPTION: 'select',
+    PARAM: 'object',
+    TBODY: 'table',
+    TD: 'table',
+    TFOOT: 'table',
+    TH: 'table',
+    THEAD: 'table',
+    TR: 'table'
+  },
+  // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
+  //       due to a Firefox bug
+  node: function(elementName) {
+    elementName = elementName.toUpperCase();
+    
+    // try innerHTML approach
+    var parentTag = this.NODEMAP[elementName] || 'div';
+    var parentElement = document.createElement(parentTag);
+    try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
+      parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
+    } catch(e) {}
+    var element = parentElement.firstChild || null;
+      
+    // see if browser added wrapping tags
+    if(element && (element.tagName.toUpperCase() != elementName))
+      element = element.getElementsByTagName(elementName)[0];
+    
+    // fallback to createElement approach
+    if(!element) element = document.createElement(elementName);
+    
+    // abort if nothing could be created
+    if(!element) return;
+
+    // attributes (or text)
+    if(arguments[1])
+      if(this._isStringOrNumber(arguments[1]) ||
+        (arguments[1] instanceof Array) ||
+        arguments[1].tagName) {
+          this._children(element, arguments[1]);
+        } else {
+          var attrs = this._attributes(arguments[1]);
+          if(attrs.length) {
+            try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
+              parentElement.innerHTML = "<" +elementName + " " +
+                attrs + "></" + elementName + ">";
+            } catch(e) {}
+            element = parentElement.firstChild || null;
+            // workaround firefox 1.0.X bug
+            if(!element) {
+              element = document.createElement(elementName);
+              for(attr in arguments[1]) 
+                element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
+            }
+            if(element.tagName.toUpperCase() != elementName)
+              element = parentElement.getElementsByTagName(elementName)[0];
+          }
+        } 
+
+    // text, or array of children
+    if(arguments[2])
+      this._children(element, arguments[2]);
+
+     return element;
+  },
+  _text: function(text) {
+     return document.createTextNode(text);
+  },
+
+  ATTR_MAP: {
+    'className': 'class',
+    'htmlFor': 'for'
+  },
+
+  _attributes: function(attributes) {
+    var attrs = [];
+    for(attribute in attributes)
+      attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
+          '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
+    return attrs.join(" ");
+  },
+  _children: function(element, children) {
+    if(children.tagName) {
+      element.appendChild(children);
+      return;
+    }
+    if(typeof children=='object') { // array can hold nodes and text
+      children.flatten().each( function(e) {
+        if(typeof e=='object')
+          element.appendChild(e)
+        else
+          if(Builder._isStringOrNumber(e))
+            element.appendChild(Builder._text(e));
+      });
+    } else
+      if(Builder._isStringOrNumber(children))
+        element.appendChild(Builder._text(children));
+  },
+  _isStringOrNumber: function(param) {
+    return(typeof param=='string' || typeof param=='number');
+  },
+  build: function(html) {
+    var element = this.node('div');
+    $(element).update(html.strip());
+    return element.down();
+  },
+  dump: function(scope) { 
+    if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope 
+  
+    var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
+      "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
+      "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
+      "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
+      "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
+      "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
+  
+    tags.each( function(tag){ 
+      scope[tag] = function() { 
+        return Builder.node.apply(Builder, [tag].concat($A(arguments)));  
+      } 
+    });
+  }
+}

Added: examples/trunk/seamspace/war/src/main/webapp/lightbox/bullet.gif
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/lightbox/bullet.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/lightbox/close.gif
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/lightbox/close.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/lightbox/closelabel.gif
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/lightbox/closelabel.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/lightbox/effects.js
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/lightbox/effects.js	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/lightbox/effects.js	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,1122 @@
+// script.aculo.us effects.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// Contributors:
+//  Justin Palmer (http://encytemedia.com/)
+//  Mark Pilgrim (http://diveintomark.org/)
+//  Martin Bialasinki
+// 
+// script.aculo.us is freely distributable under the terms of an MIT-style license.
+// For details, see the script.aculo.us web site: http://script.aculo.us/ 
+
+// converts rgb() and #xxx to #xxxxxx format,  
+// returns self (or first argument) if not convertable  
+String.prototype.parseColor = function() {  
+  var color = '#';
+  if (this.slice(0,4) == 'rgb(') {  
+    var cols = this.slice(4,this.length-1).split(',');  
+    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);  
+  } else {  
+    if (this.slice(0,1) == '#') {  
+      if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();  
+      if (this.length==7) color = this.toLowerCase();  
+    }  
+  }  
+  return (color.length==7 ? color : (arguments[0] || this));  
+};
+
+/*--------------------------------------------------------------------------*/
+
+Element.collectTextNodes = function(element) {  
+  return $A($(element).childNodes).collect( function(node) {
+    return (node.nodeType==3 ? node.nodeValue : 
+      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
+  }).flatten().join('');
+};
+
+Element.collectTextNodesIgnoreClass = function(element, className) {  
+  return $A($(element).childNodes).collect( function(node) {
+    return (node.nodeType==3 ? node.nodeValue : 
+      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? 
+        Element.collectTextNodesIgnoreClass(node, className) : ''));
+  }).flatten().join('');
+};
+
+Element.setContentZoom = function(element, percent) {
+  element = $(element);  
+  element.setStyle({fontSize: (percent/100) + 'em'});   
+  if (Prototype.Browser.WebKit) window.scrollBy(0,0);
+  return element;
+};
+
+Element.getInlineOpacity = function(element){
+  return $(element).style.opacity || '';
+};
+
+Element.forceRerendering = function(element) {
+  try {
+    element = $(element);
+    var n = document.createTextNode(' ');
+    element.appendChild(n);
+    element.removeChild(n);
+  } catch(e) { }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Effect = {
+  _elementDoesNotExistError: {
+    name: 'ElementDoesNotExistError',
+    message: 'The specified DOM element does not exist, but is required for this effect to operate'
+  },
+  Transitions: {
+    linear: Prototype.K,
+    sinoidal: function(pos) {
+      return (-Math.cos(pos*Math.PI)/2) + 0.5;
+    },
+    reverse: function(pos) {
+      return 1-pos;
+    },
+    flicker: function(pos) {
+      var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
+      return pos > 1 ? 1 : pos;
+    },
+    wobble: function(pos) {
+      return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
+    },
+    pulse: function(pos, pulses) { 
+      pulses = pulses || 5; 
+      return (
+        ((pos % (1/pulses)) * pulses).round() == 0 ? 
+              ((pos * pulses * 2) - (pos * pulses * 2).floor()) : 
+          1 - ((pos * pulses * 2) - (pos * pulses * 2).floor())
+        );
+    },
+    spring: function(pos) { 
+      return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); 
+    },
+    none: function(pos) {
+      return 0;
+    },
+    full: function(pos) {
+      return 1;
+    }
+  },
+  DefaultOptions: {
+    duration:   1.0,   // seconds
+    fps:        100,   // 100= assume 66fps max.
+    sync:       false, // true for combining
+    from:       0.0,
+    to:         1.0,
+    delay:      0.0,
+    queue:      'parallel'
+  },
+  tagifyText: function(element) {
+    var tagifyStyle = 'position:relative';
+    if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
+    
+    element = $(element);
+    $A(element.childNodes).each( function(child) {
+      if (child.nodeType==3) {
+        child.nodeValue.toArray().each( function(character) {
+          element.insertBefore(
+            new Element('span', {style: tagifyStyle}).update(
+              character == ' ' ? String.fromCharCode(160) : character), 
+              child);
+        });
+        Element.remove(child);
+      }
+    });
+  },
+  multiple: function(element, effect) {
+    var elements;
+    if (((typeof element == 'object') || 
+        Object.isFunction(element)) && 
+       (element.length))
+      elements = element;
+    else
+      elements = $(element).childNodes;
+      
+    var options = Object.extend({
+      speed: 0.1,
+      delay: 0.0
+    }, arguments[2] || { });
+    var masterDelay = options.delay;
+
+    $A(elements).each( function(element, index) {
+      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
+    });
+  },
+  PAIRS: {
+    'slide':  ['SlideDown','SlideUp'],
+    'blind':  ['BlindDown','BlindUp'],
+    'appear': ['Appear','Fade']
+  },
+  toggle: function(element, effect) {
+    element = $(element);
+    effect = (effect || 'appear').toLowerCase();
+    var options = Object.extend({
+      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
+    }, arguments[2] || { });
+    Effect[element.visible() ? 
+      Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
+  }
+};
+
+Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
+
+/* ------------- core effects ------------- */
+
+Effect.ScopedQueue = Class.create(Enumerable, {
+  initialize: function() {
+    this.effects  = [];
+    this.interval = null;    
+  },
+  _each: function(iterator) {
+    this.effects._each(iterator);
+  },
+  add: function(effect) {
+    var timestamp = new Date().getTime();
+    
+    var position = Object.isString(effect.options.queue) ? 
+      effect.options.queue : effect.options.queue.position;
+    
+    switch(position) {
+      case 'front':
+        // move unstarted effects after this effect  
+        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
+            e.startOn  += effect.finishOn;
+            e.finishOn += effect.finishOn;
+          });
+        break;
+      case 'with-last':
+        timestamp = this.effects.pluck('startOn').max() || timestamp;
+        break;
+      case 'end':
+        // start effect after last queued effect has finished
+        timestamp = this.effects.pluck('finishOn').max() || timestamp;
+        break;
+    }
+    
+    effect.startOn  += timestamp;
+    effect.finishOn += timestamp;
+
+    if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
+      this.effects.push(effect);
+    
+    if (!this.interval)
+      this.interval = setInterval(this.loop.bind(this), 15);
+  },
+  remove: function(effect) {
+    this.effects = this.effects.reject(function(e) { return e==effect });
+    if (this.effects.length == 0) {
+      clearInterval(this.interval);
+      this.interval = null;
+    }
+  },
+  loop: function() {
+    var timePos = new Date().getTime();
+    for(var i=0, len=this.effects.length;i<len;i++) 
+      this.effects[i] && this.effects[i].loop(timePos);
+  }
+});
+
+Effect.Queues = {
+  instances: $H(),
+  get: function(queueName) {
+    if (!Object.isString(queueName)) return queueName;
+    
+    return this.instances.get(queueName) ||
+      this.instances.set(queueName, new Effect.ScopedQueue());
+  }
+};
+Effect.Queue = Effect.Queues.get('global');
+
+Effect.Base = Class.create({
+  position: null,
+  start: function(options) {
+    function codeForEvent(options,eventName){
+      return (
+        (options[eventName+'Internal'] ? 'this.options.'+eventName+'Internal(this);' : '') +
+        (options[eventName] ? 'this.options.'+eventName+'(this);' : '')
+      );
+    }
+    if (options && options.transition === false) options.transition = Effect.Transitions.linear;
+    this.options      = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
+    this.currentFrame = 0;
+    this.state        = 'idle';
+    this.startOn      = this.options.delay*1000;
+    this.finishOn     = this.startOn+(this.options.duration*1000);
+    this.fromToDelta  = this.options.to-this.options.from;
+    this.totalTime    = this.finishOn-this.startOn;
+    this.totalFrames  = this.options.fps*this.options.duration;
+    
+    eval('this.render = function(pos){ '+
+      'if (this.state=="idle"){this.state="running";'+
+      codeForEvent(this.options,'beforeSetup')+
+      (this.setup ? 'this.setup();':'')+ 
+      codeForEvent(this.options,'afterSetup')+
+      '};if (this.state=="running"){'+
+      'pos=this.options.transition(pos)*'+this.fromToDelta+'+'+this.options.from+';'+
+      'this.position=pos;'+
+      codeForEvent(this.options,'beforeUpdate')+
+      (this.update ? 'this.update(pos);':'')+
+      codeForEvent(this.options,'afterUpdate')+
+      '}}');
+    
+    this.event('beforeStart');
+    if (!this.options.sync)
+      Effect.Queues.get(Object.isString(this.options.queue) ? 
+        'global' : this.options.queue.scope).add(this);
+  },
+  loop: function(timePos) {
+    if (timePos >= this.startOn) {
+      if (timePos >= this.finishOn) {
+        this.render(1.0);
+        this.cancel();
+        this.event('beforeFinish');
+        if (this.finish) this.finish(); 
+        this.event('afterFinish');
+        return;  
+      }
+      var pos   = (timePos - this.startOn) / this.totalTime,
+          frame = (pos * this.totalFrames).round();
+      if (frame > this.currentFrame) {
+        this.render(pos);
+        this.currentFrame = frame;
+      }
+    }
+  },
+  cancel: function() {
+    if (!this.options.sync)
+      Effect.Queues.get(Object.isString(this.options.queue) ? 
+        'global' : this.options.queue.scope).remove(this);
+    this.state = 'finished';
+  },
+  event: function(eventName) {
+    if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
+    if (this.options[eventName]) this.options[eventName](this);
+  },
+  inspect: function() {
+    var data = $H();
+    for(property in this)
+      if (!Object.isFunction(this[property])) data.set(property, this[property]);
+    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
+  }
+});
+
+Effect.Parallel = Class.create(Effect.Base, {
+  initialize: function(effects) {
+    this.effects = effects || [];
+    this.start(arguments[1]);
+  },
+  update: function(position) {
+    this.effects.invoke('render', position);
+  },
+  finish: function(position) {
+    this.effects.each( function(effect) {
+      effect.render(1.0);
+      effect.cancel();
+      effect.event('beforeFinish');
+      if (effect.finish) effect.finish(position);
+      effect.event('afterFinish');
+    });
+  }
+});
+
+Effect.Tween = Class.create(Effect.Base, {
+  initialize: function(object, from, to) {
+    object = Object.isString(object) ? $(object) : object;
+    var args = $A(arguments), method = args.last(), 
+      options = args.length == 5 ? args[3] : null;
+    this.method = Object.isFunction(method) ? method.bind(object) :
+      Object.isFunction(object[method]) ? object[method].bind(object) : 
+      function(value) { object[method] = value };
+    this.start(Object.extend({ from: from, to: to }, options || { }));
+  },
+  update: function(position) {
+    this.method(position);
+  }
+});
+
+Effect.Event = Class.create(Effect.Base, {
+  initialize: function() {
+    this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
+  },
+  update: Prototype.emptyFunction
+});
+
+Effect.Opacity = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    // make this work on IE on elements without 'layout'
+    if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
+      this.element.setStyle({zoom: 1});
+    var options = Object.extend({
+      from: this.element.getOpacity() || 0.0,
+      to:   1.0
+    }, arguments[1] || { });
+    this.start(options);
+  },
+  update: function(position) {
+    this.element.setOpacity(position);
+  }
+});
+
+Effect.Move = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({
+      x:    0,
+      y:    0,
+      mode: 'relative'
+    }, arguments[1] || { });
+    this.start(options);
+  },
+  setup: function() {
+    this.element.makePositioned();
+    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
+    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
+    if (this.options.mode == 'absolute') {
+      this.options.x = this.options.x - this.originalLeft;
+      this.options.y = this.options.y - this.originalTop;
+    }
+  },
+  update: function(position) {
+    this.element.setStyle({
+      left: (this.options.x  * position + this.originalLeft).round() + 'px',
+      top:  (this.options.y  * position + this.originalTop).round()  + 'px'
+    });
+  }
+});
+
+// for backwards compatibility
+Effect.MoveBy = function(element, toTop, toLeft) {
+  return new Effect.Move(element, 
+    Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
+};
+
+Effect.Scale = Class.create(Effect.Base, {
+  initialize: function(element, percent) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({
+      scaleX: true,
+      scaleY: true,
+      scaleContent: true,
+      scaleFromCenter: false,
+      scaleMode: 'box',        // 'box' or 'contents' or { } with provided values
+      scaleFrom: 100.0,
+      scaleTo:   percent
+    }, arguments[2] || { });
+    this.start(options);
+  },
+  setup: function() {
+    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
+    this.elementPositioning = this.element.getStyle('position');
+    
+    this.originalStyle = { };
+    ['top','left','width','height','fontSize'].each( function(k) {
+      this.originalStyle[k] = this.element.style[k];
+    }.bind(this));
+      
+    this.originalTop  = this.element.offsetTop;
+    this.originalLeft = this.element.offsetLeft;
+    
+    var fontSize = this.element.getStyle('font-size') || '100%';
+    ['em','px','%','pt'].each( function(fontSizeType) {
+      if (fontSize.indexOf(fontSizeType)>0) {
+        this.fontSize     = parseFloat(fontSize);
+        this.fontSizeType = fontSizeType;
+      }
+    }.bind(this));
+    
+    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
+    
+    this.dims = null;
+    if (this.options.scaleMode=='box')
+      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
+    if (/^content/.test(this.options.scaleMode))
+      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
+    if (!this.dims)
+      this.dims = [this.options.scaleMode.originalHeight,
+                   this.options.scaleMode.originalWidth];
+  },
+  update: function(position) {
+    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
+    if (this.options.scaleContent && this.fontSize)
+      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
+    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
+  },
+  finish: function(position) {
+    if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
+  },
+  setDimensions: function(height, width) {
+    var d = { };
+    if (this.options.scaleX) d.width = width.round() + 'px';
+    if (this.options.scaleY) d.height = height.round() + 'px';
+    if (this.options.scaleFromCenter) {
+      var topd  = (height - this.dims[0])/2;
+      var leftd = (width  - this.dims[1])/2;
+      if (this.elementPositioning == 'absolute') {
+        if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
+        if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
+      } else {
+        if (this.options.scaleY) d.top = -topd + 'px';
+        if (this.options.scaleX) d.left = -leftd + 'px';
+      }
+    }
+    this.element.setStyle(d);
+  }
+});
+
+Effect.Highlight = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
+    this.start(options);
+  },
+  setup: function() {
+    // Prevent executing on elements not in the layout flow
+    if (this.element.getStyle('display')=='none') { this.cancel(); return; }
+    // Disable background image during the effect
+    this.oldStyle = { };
+    if (!this.options.keepBackgroundImage) {
+      this.oldStyle.backgroundImage = this.element.getStyle('background-image');
+      this.element.setStyle({backgroundImage: 'none'});
+    }
+    if (!this.options.endcolor)
+      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
+    if (!this.options.restorecolor)
+      this.options.restorecolor = this.element.getStyle('background-color');
+    // init color calculations
+    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
+    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
+  },
+  update: function(position) {
+    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
+      return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
+  },
+  finish: function() {
+    this.element.setStyle(Object.extend(this.oldStyle, {
+      backgroundColor: this.options.restorecolor
+    }));
+  }
+});
+
+Effect.ScrollTo = function(element) {
+  var options = arguments[1] || { },
+    scrollOffsets = document.viewport.getScrollOffsets(),
+    elementOffsets = $(element).cumulativeOffset(),
+    max = (window.height || document.body.scrollHeight) - document.viewport.getHeight();  
+
+  if (options.offset) elementOffsets[1] += options.offset;
+
+  return new Effect.Tween(null,
+    scrollOffsets.top,
+    elementOffsets[1] > max ? max : elementOffsets[1],
+    options,
+    function(p){ scrollTo(scrollOffsets.left, p.round()) }
+  );
+};
+
+/* ------------- combination effects ------------- */
+
+Effect.Fade = function(element) {
+  element = $(element);
+  var oldOpacity = element.getInlineOpacity();
+  var options = Object.extend({
+    from: element.getOpacity() || 1.0,
+    to:   0.0,
+    afterFinishInternal: function(effect) { 
+      if (effect.options.to!=0) return;
+      effect.element.hide().setStyle({opacity: oldOpacity}); 
+    }
+  }, arguments[1] || { });
+  return new Effect.Opacity(element,options);
+};
+
+Effect.Appear = function(element) {
+  element = $(element);
+  var options = Object.extend({
+  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
+  to:   1.0,
+  // force Safari to render floated elements properly
+  afterFinishInternal: function(effect) {
+    effect.element.forceRerendering();
+  },
+  beforeSetup: function(effect) {
+    effect.element.setOpacity(effect.options.from).show(); 
+  }}, arguments[1] || { });
+  return new Effect.Opacity(element,options);
+};
+
+Effect.Puff = function(element) {
+  element = $(element);
+  var oldStyle = { 
+    opacity: element.getInlineOpacity(), 
+    position: element.getStyle('position'),
+    top:  element.style.top,
+    left: element.style.left,
+    width: element.style.width,
+    height: element.style.height
+  };
+  return new Effect.Parallel(
+   [ new Effect.Scale(element, 200, 
+      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), 
+     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], 
+     Object.extend({ duration: 1.0, 
+      beforeSetupInternal: function(effect) {
+        Position.absolutize(effect.effects[0].element)
+      },
+      afterFinishInternal: function(effect) {
+         effect.effects[0].element.hide().setStyle(oldStyle); }
+     }, arguments[1] || { })
+   );
+};
+
+Effect.BlindUp = function(element) {
+  element = $(element);
+  element.makeClipping();
+  return new Effect.Scale(element, 0,
+    Object.extend({ scaleContent: false, 
+      scaleX: false, 
+      restoreAfterFinish: true,
+      afterFinishInternal: function(effect) {
+        effect.element.hide().undoClipping();
+      } 
+    }, arguments[1] || { })
+  );
+};
+
+Effect.BlindDown = function(element) {
+  element = $(element);
+  var elementDimensions = element.getDimensions();
+  return new Effect.Scale(element, 100, Object.extend({ 
+    scaleContent: false, 
+    scaleX: false,
+    scaleFrom: 0,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+    restoreAfterFinish: true,
+    afterSetup: function(effect) {
+      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
+    },  
+    afterFinishInternal: function(effect) {
+      effect.element.undoClipping();
+    }
+  }, arguments[1] || { }));
+};
+
+Effect.SwitchOff = function(element) {
+  element = $(element);
+  var oldOpacity = element.getInlineOpacity();
+  return new Effect.Appear(element, Object.extend({
+    duration: 0.4,
+    from: 0,
+    transition: Effect.Transitions.flicker,
+    afterFinishInternal: function(effect) {
+      new Effect.Scale(effect.element, 1, { 
+        duration: 0.3, scaleFromCenter: true,
+        scaleX: false, scaleContent: false, restoreAfterFinish: true,
+        beforeSetup: function(effect) { 
+          effect.element.makePositioned().makeClipping();
+        },
+        afterFinishInternal: function(effect) {
+          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
+        }
+      })
+    }
+  }, arguments[1] || { }));
+};
+
+Effect.DropOut = function(element) {
+  element = $(element);
+  var oldStyle = {
+    top: element.getStyle('top'),
+    left: element.getStyle('left'),
+    opacity: element.getInlineOpacity() };
+  return new Effect.Parallel(
+    [ new Effect.Move(element, {x: 0, y: 100, sync: true }), 
+      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
+    Object.extend(
+      { duration: 0.5,
+        beforeSetup: function(effect) {
+          effect.effects[0].element.makePositioned(); 
+        },
+        afterFinishInternal: function(effect) {
+          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
+        } 
+      }, arguments[1] || { }));
+};
+
+Effect.Shake = function(element) {
+  element = $(element);
+  var options = Object.extend({
+    distance: 20,
+    duration: 0.5
+  }, arguments[1] || {});
+  var distance = parseFloat(options.distance);
+  var split = parseFloat(options.duration) / 10.0;
+  var oldStyle = {
+    top: element.getStyle('top'),
+    left: element.getStyle('left') };
+    return new Effect.Move(element,
+      { x:  distance, y: 0, duration: split, afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
+    new Effect.Move(effect.element,
+      { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
+        effect.element.undoPositioned().setStyle(oldStyle);
+  }}) }}) }}) }}) }}) }});
+};
+
+Effect.SlideDown = function(element) {
+  element = $(element).cleanWhitespace();
+  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
+  var oldInnerBottom = element.down().getStyle('bottom');
+  var elementDimensions = element.getDimensions();
+  return new Effect.Scale(element, 100, Object.extend({ 
+    scaleContent: false, 
+    scaleX: false, 
+    scaleFrom: window.opera ? 0 : 1,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+    restoreAfterFinish: true,
+    afterSetup: function(effect) {
+      effect.element.makePositioned();
+      effect.element.down().makePositioned();
+      if (window.opera) effect.element.setStyle({top: ''});
+      effect.element.makeClipping().setStyle({height: '0px'}).show(); 
+    },
+    afterUpdateInternal: function(effect) {
+      effect.element.down().setStyle({bottom:
+        (effect.dims[0] - effect.element.clientHeight) + 'px' }); 
+    },
+    afterFinishInternal: function(effect) {
+      effect.element.undoClipping().undoPositioned();
+      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
+    }, arguments[1] || { })
+  );
+};
+
+Effect.SlideUp = function(element) {
+  element = $(element).cleanWhitespace();
+  var oldInnerBottom = element.down().getStyle('bottom');
+  var elementDimensions = element.getDimensions();
+  return new Effect.Scale(element, window.opera ? 0 : 1,
+   Object.extend({ scaleContent: false, 
+    scaleX: false, 
+    scaleMode: 'box',
+    scaleFrom: 100,
+    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+    restoreAfterFinish: true,
+    afterSetup: function(effect) {
+      effect.element.makePositioned();
+      effect.element.down().makePositioned();
+      if (window.opera) effect.element.setStyle({top: ''});
+      effect.element.makeClipping().show();
+    },  
+    afterUpdateInternal: function(effect) {
+      effect.element.down().setStyle({bottom:
+        (effect.dims[0] - effect.element.clientHeight) + 'px' });
+    },
+    afterFinishInternal: function(effect) {
+      effect.element.hide().undoClipping().undoPositioned();
+      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
+    }
+   }, arguments[1] || { })
+  );
+};
+
+// Bug in opera makes the TD containing this element expand for a instance after finish 
+Effect.Squish = function(element) {
+  return new Effect.Scale(element, window.opera ? 1 : 0, { 
+    restoreAfterFinish: true,
+    beforeSetup: function(effect) {
+      effect.element.makeClipping(); 
+    },  
+    afterFinishInternal: function(effect) {
+      effect.element.hide().undoClipping(); 
+    }
+  });
+};
+
+Effect.Grow = function(element) {
+  element = $(element);
+  var options = Object.extend({
+    direction: 'center',
+    moveTransition: Effect.Transitions.sinoidal,
+    scaleTransition: Effect.Transitions.sinoidal,
+    opacityTransition: Effect.Transitions.full
+  }, arguments[1] || { });
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    height: element.style.height,
+    width: element.style.width,
+    opacity: element.getInlineOpacity() };
+
+  var dims = element.getDimensions();    
+  var initialMoveX, initialMoveY;
+  var moveX, moveY;
+  
+  switch (options.direction) {
+    case 'top-left':
+      initialMoveX = initialMoveY = moveX = moveY = 0; 
+      break;
+    case 'top-right':
+      initialMoveX = dims.width;
+      initialMoveY = moveY = 0;
+      moveX = -dims.width;
+      break;
+    case 'bottom-left':
+      initialMoveX = moveX = 0;
+      initialMoveY = dims.height;
+      moveY = -dims.height;
+      break;
+    case 'bottom-right':
+      initialMoveX = dims.width;
+      initialMoveY = dims.height;
+      moveX = -dims.width;
+      moveY = -dims.height;
+      break;
+    case 'center':
+      initialMoveX = dims.width / 2;
+      initialMoveY = dims.height / 2;
+      moveX = -dims.width / 2;
+      moveY = -dims.height / 2;
+      break;
+  }
+  
+  return new Effect.Move(element, {
+    x: initialMoveX,
+    y: initialMoveY,
+    duration: 0.01, 
+    beforeSetup: function(effect) {
+      effect.element.hide().makeClipping().makePositioned();
+    },
+    afterFinishInternal: function(effect) {
+      new Effect.Parallel(
+        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
+          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
+          new Effect.Scale(effect.element, 100, {
+            scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, 
+            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
+        ], Object.extend({
+             beforeSetup: function(effect) {
+               effect.effects[0].element.setStyle({height: '0px'}).show(); 
+             },
+             afterFinishInternal: function(effect) {
+               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); 
+             }
+           }, options)
+      )
+    }
+  });
+};
+
+Effect.Shrink = function(element) {
+  element = $(element);
+  var options = Object.extend({
+    direction: 'center',
+    moveTransition: Effect.Transitions.sinoidal,
+    scaleTransition: Effect.Transitions.sinoidal,
+    opacityTransition: Effect.Transitions.none
+  }, arguments[1] || { });
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    height: element.style.height,
+    width: element.style.width,
+    opacity: element.getInlineOpacity() };
+
+  var dims = element.getDimensions();
+  var moveX, moveY;
+  
+  switch (options.direction) {
+    case 'top-left':
+      moveX = moveY = 0;
+      break;
+    case 'top-right':
+      moveX = dims.width;
+      moveY = 0;
+      break;
+    case 'bottom-left':
+      moveX = 0;
+      moveY = dims.height;
+      break;
+    case 'bottom-right':
+      moveX = dims.width;
+      moveY = dims.height;
+      break;
+    case 'center':  
+      moveX = dims.width / 2;
+      moveY = dims.height / 2;
+      break;
+  }
+  
+  return new Effect.Parallel(
+    [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
+      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
+      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
+    ], Object.extend({            
+         beforeStartInternal: function(effect) {
+           effect.effects[0].element.makePositioned().makeClipping(); 
+         },
+         afterFinishInternal: function(effect) {
+           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
+       }, options)
+  );
+};
+
+Effect.Pulsate = function(element) {
+  element = $(element);
+  var options    = arguments[1] || { };
+  var oldOpacity = element.getInlineOpacity();
+  var transition = options.transition || Effect.Transitions.sinoidal;
+  var reverser   = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) };
+  reverser.bind(transition);
+  return new Effect.Opacity(element, 
+    Object.extend(Object.extend({  duration: 2.0, from: 0,
+      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
+    }, options), {transition: reverser}));
+};
+
+Effect.Fold = function(element) {
+  element = $(element);
+  var oldStyle = {
+    top: element.style.top,
+    left: element.style.left,
+    width: element.style.width,
+    height: element.style.height };
+  element.makeClipping();
+  return new Effect.Scale(element, 5, Object.extend({   
+    scaleContent: false,
+    scaleX: false,
+    afterFinishInternal: function(effect) {
+    new Effect.Scale(element, 1, { 
+      scaleContent: false, 
+      scaleY: false,
+      afterFinishInternal: function(effect) {
+        effect.element.hide().undoClipping().setStyle(oldStyle);
+      } });
+  }}, arguments[1] || { }));
+};
+
+Effect.Morph = Class.create(Effect.Base, {
+  initialize: function(element) {
+    this.element = $(element);
+    if (!this.element) throw(Effect._elementDoesNotExistError);
+    var options = Object.extend({
+      style: { }
+    }, arguments[1] || { });
+    
+    if (!Object.isString(options.style)) this.style = $H(options.style);
+    else {
+      if (options.style.include(':'))
+        this.style = options.style.parseStyle();
+      else {
+        this.element.addClassName(options.style);
+        this.style = $H(this.element.getStyles());
+        this.element.removeClassName(options.style);
+        var css = this.element.getStyles();
+        this.style = this.style.reject(function(style) {
+          return style.value == css[style.key];
+        });
+        options.afterFinishInternal = function(effect) {
+          effect.element.addClassName(effect.options.style);
+          effect.transforms.each(function(transform) {
+            effect.element.style[transform.style] = '';
+          });
+        }
+      }
+    }
+    this.start(options);
+  },
+  
+  setup: function(){
+    function parseColor(color){
+      if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
+      color = color.parseColor();
+      return $R(0,2).map(function(i){
+        return parseInt( color.slice(i*2+1,i*2+3), 16 ) 
+      });
+    }
+    this.transforms = this.style.map(function(pair){
+      var property = pair[0], value = pair[1], unit = null;
+
+      if (value.parseColor('#zzzzzz') != '#zzzzzz') {
+        value = value.parseColor();
+        unit  = 'color';
+      } else if (property == 'opacity') {
+        value = parseFloat(value);
+        if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
+          this.element.setStyle({zoom: 1});
+      } else if (Element.CSS_LENGTH.test(value)) {
+          var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
+          value = parseFloat(components[1]);
+          unit = (components.length == 3) ? components[2] : null;
+      }
+
+      var originalValue = this.element.getStyle(property);
+      return { 
+        style: property.camelize(), 
+        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), 
+        targetValue: unit=='color' ? parseColor(value) : value,
+        unit: unit
+      };
+    }.bind(this)).reject(function(transform){
+      return (
+        (transform.originalValue == transform.targetValue) ||
+        (
+          transform.unit != 'color' &&
+          (isNaN(transform.originalValue) || isNaN(transform.targetValue))
+        )
+      )
+    });
+  },
+  update: function(position) {
+    var style = { }, transform, i = this.transforms.length;
+    while(i--)
+      style[(transform = this.transforms[i]).style] = 
+        transform.unit=='color' ? '#'+
+          (Math.round(transform.originalValue[0]+
+            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
+          (Math.round(transform.originalValue[1]+
+            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
+          (Math.round(transform.originalValue[2]+
+            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
+        (transform.originalValue +
+          (transform.targetValue - transform.originalValue) * position).toFixed(3) + 
+            (transform.unit === null ? '' : transform.unit);
+    this.element.setStyle(style, true);
+  }
+});
+
+Effect.Transform = Class.create({
+  initialize: function(tracks){
+    this.tracks  = [];
+    this.options = arguments[1] || { };
+    this.addTracks(tracks);
+  },
+  addTracks: function(tracks){
+    tracks.each(function(track){
+      track = $H(track);
+      var data = track.values().first();
+      this.tracks.push($H({
+        ids:     track.keys().first(),
+        effect:  Effect.Morph,
+        options: { style: data }
+      }));
+    }.bind(this));
+    return this;
+  },
+  play: function(){
+    return new Effect.Parallel(
+      this.tracks.map(function(track){
+        var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
+        var elements = [$(ids) || $$(ids)].flatten();
+        return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
+      }).flatten(),
+      this.options
+    );
+  }
+});
+
+Element.CSS_PROPERTIES = $w(
+  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + 
+  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
+  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
+  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
+  'fontSize fontWeight height left letterSpacing lineHeight ' +
+  'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
+  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
+  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
+  'right textIndent top width wordSpacing zIndex');
+  
+Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
+
+String.__parseStyleElement = document.createElement('div');
+String.prototype.parseStyle = function(){
+  var style, styleRules = $H();
+  if (Prototype.Browser.WebKit)
+    style = new Element('div',{style:this}).style;
+  else {
+    String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
+    style = String.__parseStyleElement.childNodes[0].style;
+  }
+  
+  Element.CSS_PROPERTIES.each(function(property){
+    if (style[property]) styleRules.set(property, style[property]); 
+  });
+  
+  if (Prototype.Browser.IE && this.include('opacity'))
+    styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
+
+  return styleRules;
+};
+
+if (document.defaultView && document.defaultView.getComputedStyle) {
+  Element.getStyles = function(element) {
+    var css = document.defaultView.getComputedStyle($(element), null);
+    return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
+      styles[property] = css[property];
+      return styles;
+    });
+  };
+} else {
+  Element.getStyles = function(element) {
+    element = $(element);
+    var css = element.currentStyle, styles;
+    styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
+      results[property] = css[property];
+      return results;
+    });
+    if (!styles.opacity) styles.opacity = element.getOpacity();
+    return styles;
+  };
+};
+
+Effect.Methods = {
+  morph: function(element, style) {
+    element = $(element);
+    new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
+    return element;
+  },
+  visualEffect: function(element, effect, options) {
+    element = $(element)
+    var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
+    new Effect[klass](element, options);
+    return element;
+  },
+  highlight: function(element, options) {
+    element = $(element);
+    new Effect.Highlight(element, options);
+    return element;
+  }
+};
+
+$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
+  'pulsate shake puff squish switchOff dropOut').each(
+  function(effect) { 
+    Effect.Methods[effect] = function(element, options){
+      element = $(element);
+      Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
+      return element;
+    }
+  }
+);
+
+$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( 
+  function(f) { Effect.Methods[f] = Element[f]; }
+);
+
+Element.addMethods(Effect.Methods);

Added: examples/trunk/seamspace/war/src/main/webapp/lightbox/lightbox.css
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/lightbox/lightbox.css	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/lightbox/lightbox.css	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,27 @@
+#lightbox{	position: absolute;	left: 0; width: 100%; z-index: 100; text-align: center; line-height: 0;}
+#lightbox img{ width: auto; height: auto;}
+#lightbox a img{ border: none; }
+
+#outerImageContainer{ position: relative; background-color: #fff; width: 250px; height: 250px; margin: 0 auto; }
+#imageContainer{ padding: 10px; }
+
+#loading{ position: absolute; top: 40%; left: 0%; height: 25%; width: 100%; text-align: center; line-height: 0; }
+#hoverNav{ position: absolute; top: 0; left: 0; height: 100%; width: 100%; z-index: 10; }
+#imageContainer>#hoverNav{ left: 0;}
+#hoverNav a{ outline: none;}
+
+#prevLink, #nextLink{ width: 49%; height: 100%; background-image: url(data:image/gif;base64,AAAA); /* Trick IE into showing hover */ display: block; }
+#prevLink { left: 0; float: left;}
+#nextLink { right: 0; float: right;}
+#prevLink:hover, #prevLink:visited:hover { background: url(prevlabel.gif) left 15% no-repeat; }
+#nextLink:hover, #nextLink:visited:hover { background: url(nextlabel.gif) right 15% no-repeat; }
+
+#imageDataContainer{ font: 10px Verdana, Helvetica, sans-serif; background-color: #fff; margin: 0 auto; line-height: 1.4em; overflow: auto; width: 100%	; }
+
+#imageData{	padding:0 10px; color: #666; }
+#imageData #imageDetails{ width: 70%; float: left; text-align: left; }	
+#imageData #caption{ font-weight: bold;	}
+#imageData #numberDisplay{ display: block; clear: left; padding-bottom: 1.0em;	}			
+#imageData #bottomNavClose{ width: 66px; float: right;  padding-bottom: 0.7em; outline: none;}	 	
+
+#overlay{ position: absolute; top: 0; left: 0; z-index: 90; width: 100%; height: 500px; background-color: #000; }

Added: examples/trunk/seamspace/war/src/main/webapp/lightbox/lightbox.js
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/lightbox/lightbox.js	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/lightbox/lightbox.js	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,497 @@
+// -----------------------------------------------------------------------------------
+//
+//	Lightbox v2.04
+//	by Lokesh Dhakar - http://www.lokeshdhakar.com
+//	Last Modification: 2/9/08
+//
+//	For more information, visit:
+//	http://lokeshdhakar.com/projects/lightbox2/
+//
+//	Licensed under the Creative Commons Attribution 2.5 License - http://creativecommons.org/licenses/by/2.5/
+//  	- Free for use in both personal and commercial projects
+//		- Attribution requires leaving author name, author link, and the license info intact.
+//	
+//  Thanks: Scott Upton(uptonic.com), Peter-Paul Koch(quirksmode.com), and Thomas Fuchs(mir.aculo.us) for ideas, libs, and snippets.
+//  		Artemy Tregubenko (arty.name) for cleanup and help in updating to latest ver of proto-aculous.
+//
+// -----------------------------------------------------------------------------------
+/*
+
+    Table of Contents
+    -----------------
+    Configuration
+
+    Lightbox Class Declaration
+    - initialize()
+    - updateImageList()
+    - start()
+    - changeImage()
+    - resizeImageContainer()
+    - showImage()
+    - updateDetails()
+    - updateNav()
+    - enableKeyboardNav()
+    - disableKeyboardNav()
+    - keyboardAction()
+    - preloadNeighborImages()
+    - end()
+    
+    Function Calls
+    - document.observe()
+   
+*/
+// -----------------------------------------------------------------------------------
+
+//
+//  Configurationl
+//
+LightboxOptions = Object.extend({
+    fileLoadingImage:        'lightbox/loading.gif',     
+    fileBottomNavCloseImage: 'lightbox/closelabel.gif',
+
+    overlayOpacity: 0.8,   // controls transparency of shadow overlay
+
+    animate: true,         // toggles resizing animations
+    resizeSpeed: 7,        // controls the speed of the image resizing animations (1=slowest and 10=fastest)
+
+    borderSize: 10,         //if you adjust the padding in the CSS, you will need to update this variable
+
+	// When grouping images this is used to write: Image # of #.
+	// Change it for non-english localization
+	labelImage: "Image",
+	labelOf: "of"
+}, window.LightboxOptions || {});
+
+// -----------------------------------------------------------------------------------
+
+var Lightbox = Class.create();
+
+Lightbox.prototype = {
+    imageArray: [],
+    activeImage: undefined,
+    
+    // initialize()
+    // Constructor runs on completion of the DOM loading. Calls updateImageList and then
+    // the function inserts html at the bottom of the page which is used to display the shadow 
+    // overlay and the image container.
+    //
+    initialize: function() {    
+        
+        this.updateImageList();
+        
+        this.keyboardAction = this.keyboardAction.bindAsEventListener(this);
+
+        if (LightboxOptions.resizeSpeed > 10) LightboxOptions.resizeSpeed = 10;
+        if (LightboxOptions.resizeSpeed < 1)  LightboxOptions.resizeSpeed = 1;
+
+	    this.resizeDuration = LightboxOptions.animate ? ((11 - LightboxOptions.resizeSpeed) * 0.15) : 0;
+	    this.overlayDuration = LightboxOptions.animate ? 0.2 : 0;  // shadow fade in/out duration
+
+        // When Lightbox starts it will resize itself from 250 by 250 to the current image dimension.
+        // If animations are turned off, it will be hidden as to prevent a flicker of a
+        // white 250 by 250 box.
+        var size = (LightboxOptions.animate ? 250 : 1) + 'px';
+        
+
+        // Code inserts html at the bottom of the page that looks similar to this:
+        //
+        //  <div id="overlay"></div>
+        //  <div id="lightbox">
+        //      <div id="outerImageContainer">
+        //          <div id="imageContainer">
+        //              <img id="lightboxImage">
+        //              <div style="" id="hoverNav">
+        //                  <a href="#" id="prevLink"></a>
+        //                  <a href="#" id="nextLink"></a>
+        //              </div>
+        //              <div id="loading">
+        //                  <a href="#" id="loadingLink">
+        //                      <img src="images/loading.gif">
+        //                  </a>
+        //              </div>
+        //          </div>
+        //      </div>
+        //      <div id="imageDataContainer">
+        //          <div id="imageData">
+        //              <div id="imageDetails">
+        //                  <span id="caption"></span>
+        //                  <span id="numberDisplay"></span>
+        //              </div>
+        //              <div id="bottomNav">
+        //                  <a href="#" id="bottomNavClose">
+        //                      <img src="images/close.gif">
+        //                  </a>
+        //              </div>
+        //          </div>
+        //      </div>
+        //  </div>
+
+
+        var objBody = $$('body')[0];
+
+		objBody.appendChild(Builder.node('div',{id:'overlay'}));
+	
+        objBody.appendChild(Builder.node('div',{id:'lightbox'}, [
+            Builder.node('div',{id:'outerImageContainer'}, 
+                Builder.node('div',{id:'imageContainer'}, [
+                    Builder.node('img',{id:'lightboxImage'}), 
+                    Builder.node('div',{id:'hoverNav'}, [
+                        Builder.node('a',{id:'prevLink', href: '#' }),
+                        Builder.node('a',{id:'nextLink', href: '#' })
+                    ]),
+                    Builder.node('div',{id:'loading'}, 
+                        Builder.node('a',{id:'loadingLink', href: '#' }, 
+                            Builder.node('img', {src: LightboxOptions.fileLoadingImage})
+                        )
+                    )
+                ])
+            ),
+            Builder.node('div', {id:'imageDataContainer'},
+                Builder.node('div',{id:'imageData'}, [
+                    Builder.node('div',{id:'imageDetails'}, [
+                        Builder.node('span',{id:'caption'}),
+                        Builder.node('span',{id:'numberDisplay'})
+                    ]),
+                    Builder.node('div',{id:'bottomNav'},
+                        Builder.node('a',{id:'bottomNavClose', href: '#' },
+                            Builder.node('img', { src: LightboxOptions.fileBottomNavCloseImage })
+                        )
+                    )
+                ])
+            )
+        ]));
+
+
+		$('overlay').hide().observe('click', (function() { this.end(); }).bind(this));
+		$('lightbox').hide().observe('click', (function(event) { if (event.element().id == 'lightbox') this.end(); }).bind(this));
+		$('outerImageContainer').setStyle({ width: size, height: size });
+		$('prevLink').observe('click', (function(event) { event.stop(); this.changeImage(this.activeImage - 1); }).bindAsEventListener(this));
+		$('nextLink').observe('click', (function(event) { event.stop(); this.changeImage(this.activeImage + 1); }).bindAsEventListener(this));
+		$('loadingLink').observe('click', (function(event) { event.stop(); this.end(); }).bind(this));
+		$('bottomNavClose').observe('click', (function(event) { event.stop(); this.end(); }).bind(this));
+
+        var th = this;
+        (function(){
+            var ids = 
+                'overlay lightbox outerImageContainer imageContainer lightboxImage hoverNav prevLink nextLink loading loadingLink ' + 
+                'imageDataContainer imageData imageDetails caption numberDisplay bottomNav bottomNavClose';   
+            $w(ids).each(function(id){ th[id] = $(id); });
+        }).defer();
+    },
+
+    //
+    // updateImageList()
+    // Loops through anchor tags looking for 'lightbox' references and applies onclick
+    // events to appropriate links. You can rerun after dynamically adding images w/ajax.
+    //
+    updateImageList: function() {   
+        this.updateImageList = Prototype.emptyFunction;
+
+        document.observe('click', (function(event){
+            var target = event.findElement('a[rel^=lightbox]') || event.findElement('area[rel^=lightbox]');
+            if (target) {
+                event.stop();
+                this.start(target);
+            }
+        }).bind(this));
+    },
+    
+    //
+    //  start()
+    //  Display overlay and lightbox. If image is part of a set, add siblings to imageArray.
+    //
+    start: function(imageLink) {    
+
+        $$('select', 'object', 'embed').each(function(node){ node.style.visibility = 'hidden' });
+
+        // stretch overlay to fill page and fade in
+        var arrayPageSize = this.getPageSize();
+        $('overlay').setStyle({ width: arrayPageSize[0] + 'px', height: arrayPageSize[1] + 'px' });
+
+        new Effect.Appear(this.overlay, { duration: this.overlayDuration, from: 0.0, to: LightboxOptions.overlayOpacity });
+
+        this.imageArray = [];
+        var imageNum = 0;       
+
+        if ((imageLink.rel == 'lightbox')){
+            // if image is NOT part of a set, add single image to imageArray
+            this.imageArray.push([imageLink.href, imageLink.title]);         
+        } else {
+            // if image is part of a set..
+            this.imageArray = 
+                $$(imageLink.tagName + '[href][rel="' + imageLink.rel + '"]').
+                collect(function(anchor){ return [anchor.href, anchor.title]; }).
+                uniq();
+            
+            while (this.imageArray[imageNum][0] != imageLink.href) { imageNum++; }
+        }
+
+        // calculate top and left offset for the lightbox 
+        var arrayPageScroll = document.viewport.getScrollOffsets();
+        var lightboxTop = arrayPageScroll[1] + (document.viewport.getHeight() / 10);
+        var lightboxLeft = arrayPageScroll[0];
+        this.lightbox.setStyle({ top: lightboxTop + 'px', left: lightboxLeft + 'px' }).show();
+        
+        this.changeImage(imageNum);
+    },
+
+    //
+    //  changeImage()
+    //  Hide most elements and preload image in preparation for resizing image container.
+    //
+    changeImage: function(imageNum) {   
+        
+        this.activeImage = imageNum; // update global var
+
+        // hide elements during transition
+        if (LightboxOptions.animate) this.loading.show();
+        this.lightboxImage.hide();
+        this.hoverNav.hide();
+        this.prevLink.hide();
+        this.nextLink.hide();
+		// HACK: Opera9 does not currently support scriptaculous opacity and appear fx
+        this.imageDataContainer.setStyle({opacity: .0001});
+        this.numberDisplay.hide();      
+        
+        var imgPreloader = new Image();
+        
+        // once image is preloaded, resize image container
+
+
+        imgPreloader.onload = (function(){
+            this.lightboxImage.src = this.imageArray[this.activeImage][0];
+            this.resizeImageContainer(imgPreloader.width, imgPreloader.height);
+        }).bind(this);
+        imgPreloader.src = this.imageArray[this.activeImage][0];
+    },
+
+    //
+    //  resizeImageContainer()
+    //
+    resizeImageContainer: function(imgWidth, imgHeight) {
+
+        // get current width and height
+        var widthCurrent  = this.outerImageContainer.getWidth();
+        var heightCurrent = this.outerImageContainer.getHeight();
+
+        // get new width and height
+        var widthNew  = (imgWidth  + LightboxOptions.borderSize * 2);
+        var heightNew = (imgHeight + LightboxOptions.borderSize * 2);
+
+        // scalars based on change from old to new
+        var xScale = (widthNew  / widthCurrent)  * 100;
+        var yScale = (heightNew / heightCurrent) * 100;
+
+        // calculate size difference between new and old image, and resize if necessary
+        var wDiff = widthCurrent - widthNew;
+        var hDiff = heightCurrent - heightNew;
+
+        if (hDiff != 0) new Effect.Scale(this.outerImageContainer, yScale, {scaleX: false, duration: this.resizeDuration, queue: 'front'}); 
+        if (wDiff != 0) new Effect.Scale(this.outerImageContainer, xScale, {scaleY: false, duration: this.resizeDuration, delay: this.resizeDuration}); 
+
+        // if new and old image are same size and no scaling transition is necessary, 
+        // do a quick pause to prevent image flicker.
+        var timeout = 0;
+        if ((hDiff == 0) && (wDiff == 0)){
+            timeout = 100;
+            if (Prototype.Browser.IE) timeout = 250;   
+        }
+
+        (function(){
+            this.prevLink.setStyle({ height: imgHeight + 'px' });
+            this.nextLink.setStyle({ height: imgHeight + 'px' });
+            this.imageDataContainer.setStyle({ width: widthNew + 'px' });
+
+            this.showImage();
+        }).bind(this).delay(timeout / 1000);
+    },
+    
+    //
+    //  showImage()
+    //  Display image and begin preloading neighbors.
+    //
+    showImage: function(){
+        this.loading.hide();
+        new Effect.Appear(this.lightboxImage, { 
+            duration: this.resizeDuration, 
+            queue: 'end', 
+            afterFinish: (function(){ this.updateDetails(); }).bind(this) 
+        });
+        this.preloadNeighborImages();
+    },
+
+    //
+    //  updateDetails()
+    //  Display caption, image number, and bottom nav.
+    //
+    updateDetails: function() {
+    
+        // if caption is not null
+//        if (this.imageArray[this.activeImage][1] != ""){
+            this.caption.update(this.imageArray[this.activeImage][1]).show();
+//        }
+        
+        // if image is part of set display 'Image x of x' 
+        if (this.imageArray.length > 1){
+            this.numberDisplay.update( LightboxOptions.labelImage + ' ' + (this.activeImage + 1) + ' ' + LightboxOptions.labelOf + '  ' + this.imageArray.length).show();
+        }
+
+        new Effect.Parallel(
+            [ 
+                new Effect.SlideDown(this.imageDataContainer, { sync: true, duration: this.resizeDuration, from: 0.0, to: 1.0 }), 
+                new Effect.Appear(this.imageDataContainer, { sync: true, duration: this.resizeDuration }) 
+            ], 
+            { 
+                duration: this.resizeDuration, 
+                afterFinish: (function() {
+	                // update overlay size and update nav
+	                var arrayPageSize = this.getPageSize();
+	                this.overlay.setStyle({ height: arrayPageSize[1] + 'px' });
+	                this.updateNav();
+                }).bind(this)
+            } 
+        );
+    },
+
+    //
+    //  updateNav()
+    //  Display appropriate previous and next hover navigation.
+    //
+    updateNav: function() {
+
+        this.hoverNav.show();               
+
+        // if not first image in set, display prev image button
+        if (this.activeImage > 0) this.prevLink.show();
+
+        // if not last image in set, display next image button
+        if (this.activeImage < (this.imageArray.length - 1)) this.nextLink.show();
+        
+        this.enableKeyboardNav();
+    },
+
+    //
+    //  enableKeyboardNav()
+    //
+    enableKeyboardNav: function() {
+        document.observe('keydown', this.keyboardAction); 
+    },
+
+    //
+    //  disableKeyboardNav()
+    //
+    disableKeyboardNav: function() {
+        document.stopObserving('keydown', this.keyboardAction); 
+    },
+
+    //
+    //  keyboardAction()
+    //
+    keyboardAction: function(event) {
+        var keycode = event.keyCode;
+
+        var escapeKey;
+        if (event.DOM_VK_ESCAPE) {  // mozilla
+            escapeKey = event.DOM_VK_ESCAPE;
+        } else { // ie
+            escapeKey = 27;
+        }
+
+        var key = String.fromCharCode(keycode).toLowerCase();
+        
+        if (key.match(/x|o|c/) || (keycode == escapeKey)){ // close lightbox
+            this.end();
+        } else if ((key == 'p') || (keycode == 37)){ // display previous image
+            if (this.activeImage != 0){
+                this.disableKeyboardNav();
+                this.changeImage(this.activeImage - 1);
+            }
+        } else if ((key == 'n') || (keycode == 39)){ // display next image
+            if (this.activeImage != (this.imageArray.length - 1)){
+                this.disableKeyboardNav();
+                this.changeImage(this.activeImage + 1);
+            }
+        }
+    },
+
+    //
+    //  preloadNeighborImages()
+    //  Preload previous and next images.
+    //
+    preloadNeighborImages: function(){
+        var preloadNextImage, preloadPrevImage;
+        if (this.imageArray.length > this.activeImage + 1){
+            preloadNextImage = new Image();
+            preloadNextImage.src = this.imageArray[this.activeImage + 1][0];
+        }
+        if (this.activeImage > 0){
+            preloadPrevImage = new Image();
+            preloadPrevImage.src = this.imageArray[this.activeImage - 1][0];
+        }
+    
+    },
+
+    //
+    //  end()
+    //
+    end: function() {
+        this.disableKeyboardNav();
+        this.lightbox.hide();
+        new Effect.Fade(this.overlay, { duration: this.overlayDuration });
+        $$('select', 'object', 'embed').each(function(node){ node.style.visibility = 'visible' });
+    },
+
+    //
+    //  getPageSize()
+    //
+    getPageSize: function() {
+	        
+	     var xScroll, yScroll;
+		
+		if (window.innerHeight && window.scrollMaxY) {	
+			xScroll = window.innerWidth + window.scrollMaxX;
+			yScroll = window.innerHeight + window.scrollMaxY;
+		} else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
+			xScroll = document.body.scrollWidth;
+			yScroll = document.body.scrollHeight;
+		} else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
+			xScroll = document.body.offsetWidth;
+			yScroll = document.body.offsetHeight;
+		}
+		
+		var windowWidth, windowHeight;
+		
+		if (self.innerHeight) {	// all except Explorer
+			if(document.documentElement.clientWidth){
+				windowWidth = document.documentElement.clientWidth; 
+			} else {
+				windowWidth = self.innerWidth;
+			}
+			windowHeight = self.innerHeight;
+		} else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
+			windowWidth = document.documentElement.clientWidth;
+			windowHeight = document.documentElement.clientHeight;
+		} else if (document.body) { // other Explorers
+			windowWidth = document.body.clientWidth;
+			windowHeight = document.body.clientHeight;
+		}	
+		
+		// for small pages with total height less then height of the viewport
+		if(yScroll < windowHeight){
+			pageHeight = windowHeight;
+		} else { 
+			pageHeight = yScroll;
+		}
+	
+		// for small pages with total width less then width of the viewport
+		if(xScroll < windowWidth){	
+			pageWidth = xScroll;		
+		} else {
+			pageWidth = windowWidth;
+		}
+
+		return [pageWidth,pageHeight];
+	}
+}
+
+document.observe('dom:loaded', function () { new Lightbox(); });
\ No newline at end of file

Added: examples/trunk/seamspace/war/src/main/webapp/lightbox/loading.gif
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/lightbox/loading.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/lightbox/nextlabel.gif
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/lightbox/nextlabel.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/lightbox/prevlabel.gif
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/lightbox/prevlabel.gif
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/lightbox/prototype.js
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/lightbox/prototype.js	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/lightbox/prototype.js	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,4221 @@
+/*  Prototype JavaScript framework, version 1.6.0.2
+ *  (c) 2005-2008 Sam Stephenson
+ *
+ *  Prototype is freely distributable under the terms of an MIT-style license.
+ *  For details, see the Prototype web site: http://www.prototypejs.org/
+ *
+ *--------------------------------------------------------------------------*/
+
+var Prototype = {
+  Version: '1.6.0.2',
+
+  Browser: {
+    IE:     !!(window.attachEvent && !window.opera),
+    Opera:  !!window.opera,
+    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
+    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1,
+    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
+  },
+
+  BrowserFeatures: {
+    XPath: !!document.evaluate,
+    ElementExtensions: !!window.HTMLElement,
+    SpecificElementExtensions:
+      document.createElement('div').__proto__ &&
+      document.createElement('div').__proto__ !==
+        document.createElement('form').__proto__
+  },
+
+  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
+  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
+
+  emptyFunction: function() { },
+  K: function(x) { return x }
+};
+
+if (Prototype.Browser.MobileSafari)
+  Prototype.BrowserFeatures.SpecificElementExtensions = false;
+
+
+/* Based on Alex Arnell's inheritance implementation. */
+var Class = {
+  create: function() {
+    var parent = null, properties = $A(arguments);
+    if (Object.isFunction(properties[0]))
+      parent = properties.shift();
+
+    function klass() {
+      this.initialize.apply(this, arguments);
+    }
+
+    Object.extend(klass, Class.Methods);
+    klass.superclass = parent;
+    klass.subclasses = [];
+
+    if (parent) {
+      var subclass = function() { };
+      subclass.prototype = parent.prototype;
+      klass.prototype = new subclass;
+      parent.subclasses.push(klass);
+    }
+
+    for (var i = 0; i < properties.length; i++)
+      klass.addMethods(properties[i]);
+
+    if (!klass.prototype.initialize)
+      klass.prototype.initialize = Prototype.emptyFunction;
+
+    klass.prototype.constructor = klass;
+
+    return klass;
+  }
+};
+
+Class.Methods = {
+  addMethods: function(source) {
+    var ancestor   = this.superclass && this.superclass.prototype;
+    var properties = Object.keys(source);
+
+    if (!Object.keys({ toString: true }).length)
+      properties.push("toString", "valueOf");
+
+    for (var i = 0, length = properties.length; i < length; i++) {
+      var property = properties[i], value = source[property];
+      if (ancestor && Object.isFunction(value) &&
+          value.argumentNames().first() == "$super") {
+        var method = value, value = Object.extend((function(m) {
+          return function() { return ancestor[m].apply(this, arguments) };
+        })(property).wrap(method), {
+          valueOf:  function() { return method },
+          toString: function() { return method.toString() }
+        });
+      }
+      this.prototype[property] = value;
+    }
+
+    return this;
+  }
+};
+
+var Abstract = { };
+
+Object.extend = function(destination, source) {
+  for (var property in source)
+    destination[property] = source[property];
+  return destination;
+};
+
+Object.extend(Object, {
+  inspect: function(object) {
+    try {
+      if (Object.isUndefined(object)) return 'undefined';
+      if (object === null) return 'null';
+      return object.inspect ? object.inspect() : String(object);
+    } catch (e) {
+      if (e instanceof RangeError) return '...';
+      throw e;
+    }
+  },
+
+  toJSON: function(object) {
+    var type = typeof object;
+    switch (type) {
+      case 'undefined':
+      case 'function':
+      case 'unknown': return;
+      case 'boolean': return object.toString();
+    }
+
+    if (object === null) return 'null';
+    if (object.toJSON) return object.toJSON();
+    if (Object.isElement(object)) return;
+
+    var results = [];
+    for (var property in object) {
+      var value = Object.toJSON(object[property]);
+      if (!Object.isUndefined(value))
+        results.push(property.toJSON() + ': ' + value);
+    }
+
+    return '{' + results.join(', ') + '}';
+  },
+
+  toQueryString: function(object) {
+    return $H(object).toQueryString();
+  },
+
+  toHTML: function(object) {
+    return object && object.toHTML ? object.toHTML() : String.interpret(object);
+  },
+
+  keys: function(object) {
+    var keys = [];
+    for (var property in object)
+      keys.push(property);
+    return keys;
+  },
+
+  values: function(object) {
+    var values = [];
+    for (var property in object)
+      values.push(object[property]);
+    return values;
+  },
+
+  clone: function(object) {
+    return Object.extend({ }, object);
+  },
+
+  isElement: function(object) {
+    return object && object.nodeType == 1;
+  },
+
+  isArray: function(object) {
+    return object != null && typeof object == "object" &&
+      'splice' in object && 'join' in object;
+  },
+
+  isHash: function(object) {
+    return object instanceof Hash;
+  },
+
+  isFunction: function(object) {
+    return typeof object == "function";
+  },
+
+  isString: function(object) {
+    return typeof object == "string";
+  },
+
+  isNumber: function(object) {
+    return typeof object == "number";
+  },
+
+  isUndefined: function(object) {
+    return typeof object == "undefined";
+  }
+});
+
+Object.extend(Function.prototype, {
+  argumentNames: function() {
+    var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip");
+    return names.length == 1 && !names[0] ? [] : names;
+  },
+
+  bind: function() {
+    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
+    var __method = this, args = $A(arguments), object = args.shift();
+    return function() {
+      return __method.apply(object, args.concat($A(arguments)));
+    }
+  },
+
+  bindAsEventListener: function() {
+    var __method = this, args = $A(arguments), object = args.shift();
+    return function(event) {
+      return __method.apply(object, [event || window.event].concat(args));
+    }
+  },
+
+  curry: function() {
+    if (!arguments.length) return this;
+    var __method = this, args = $A(arguments);
+    return function() {
+      return __method.apply(this, args.concat($A(arguments)));
+    }
+  },
+
+  delay: function() {
+    var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
+    return window.setTimeout(function() {
+      return __method.apply(__method, args);
+    }, timeout);
+  },
+
+  wrap: function(wrapper) {
+    var __method = this;
+    return function() {
+      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
+    }
+  },
+
+  methodize: function() {
+    if (this._methodized) return this._methodized;
+    var __method = this;
+    return this._methodized = function() {
+      return __method.apply(null, [this].concat($A(arguments)));
+    };
+  }
+});
+
+Function.prototype.defer = Function.prototype.delay.curry(0.01);
+
+Date.prototype.toJSON = function() {
+  return '"' + this.getUTCFullYear() + '-' +
+    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
+    this.getUTCDate().toPaddedString(2) + 'T' +
+    this.getUTCHours().toPaddedString(2) + ':' +
+    this.getUTCMinutes().toPaddedString(2) + ':' +
+    this.getUTCSeconds().toPaddedString(2) + 'Z"';
+};
+
+var Try = {
+  these: function() {
+    var returnValue;
+
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      var lambda = arguments[i];
+      try {
+        returnValue = lambda();
+        break;
+      } catch (e) { }
+    }
+
+    return returnValue;
+  }
+};
+
+RegExp.prototype.match = RegExp.prototype.test;
+
+RegExp.escape = function(str) {
+  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
+};
+
+/*--------------------------------------------------------------------------*/
+
+var PeriodicalExecuter = Class.create({
+  initialize: function(callback, frequency) {
+    this.callback = callback;
+    this.frequency = frequency;
+    this.currentlyExecuting = false;
+
+    this.registerCallback();
+  },
+
+  registerCallback: function() {
+    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
+  },
+
+  execute: function() {
+    this.callback(this);
+  },
+
+  stop: function() {
+    if (!this.timer) return;
+    clearInterval(this.timer);
+    this.timer = null;
+  },
+
+  onTimerEvent: function() {
+    if (!this.currentlyExecuting) {
+      try {
+        this.currentlyExecuting = true;
+        this.execute();
+      } finally {
+        this.currentlyExecuting = false;
+      }
+    }
+  }
+});
+Object.extend(String, {
+  interpret: function(value) {
+    return value == null ? '' : String(value);
+  },
+  specialChar: {
+    '\b': '\\b',
+    '\t': '\\t',
+    '\n': '\\n',
+    '\f': '\\f',
+    '\r': '\\r',
+    '\\': '\\\\'
+  }
+});
+
+Object.extend(String.prototype, {
+  gsub: function(pattern, replacement) {
+    var result = '', source = this, match;
+    replacement = arguments.callee.prepareReplacement(replacement);
+
+    while (source.length > 0) {
+      if (match = source.match(pattern)) {
+        result += source.slice(0, match.index);
+        result += String.interpret(replacement(match));
+        source  = source.slice(match.index + match[0].length);
+      } else {
+        result += source, source = '';
+      }
+    }
+    return result;
+  },
+
+  sub: function(pattern, replacement, count) {
+    replacement = this.gsub.prepareReplacement(replacement);
+    count = Object.isUndefined(count) ? 1 : count;
+
+    return this.gsub(pattern, function(match) {
+      if (--count < 0) return match[0];
+      return replacement(match);
+    });
+  },
+
+  scan: function(pattern, iterator) {
+    this.gsub(pattern, iterator);
+    return String(this);
+  },
+
+  truncate: function(length, truncation) {
+    length = length || 30;
+    truncation = Object.isUndefined(truncation) ? '...' : truncation;
+    return this.length > length ?
+      this.slice(0, length - truncation.length) + truncation : String(this);
+  },
+
+  strip: function() {
+    return this.replace(/^\s+/, '').replace(/\s+$/, '');
+  },
+
+  stripTags: function() {
+    return this.replace(/<\/?[^>]+>/gi, '');
+  },
+
+  stripScripts: function() {
+    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
+  },
+
+  extractScripts: function() {
+    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
+    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
+    return (this.match(matchAll) || []).map(function(scriptTag) {
+      return (scriptTag.match(matchOne) || ['', ''])[1];
+    });
+  },
+
+  evalScripts: function() {
+    return this.extractScripts().map(function(script) { return eval(script) });
+  },
+
+  escapeHTML: function() {
+    var self = arguments.callee;
+    self.text.data = this;
+    return self.div.innerHTML;
+  },
+
+  unescapeHTML: function() {
+    var div = new Element('div');
+    div.innerHTML = this.stripTags();
+    return div.childNodes[0] ? (div.childNodes.length > 1 ?
+      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
+      div.childNodes[0].nodeValue) : '';
+  },
+
+  toQueryParams: function(separator) {
+    var match = this.strip().match(/([^?#]*)(#.*)?$/);
+    if (!match) return { };
+
+    return match[1].split(separator || '&').inject({ }, function(hash, pair) {
+      if ((pair = pair.split('='))[0]) {
+        var key = decodeURIComponent(pair.shift());
+        var value = pair.length > 1 ? pair.join('=') : pair[0];
+        if (value != undefined) value = decodeURIComponent(value);
+
+        if (key in hash) {
+          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
+          hash[key].push(value);
+        }
+        else hash[key] = value;
+      }
+      return hash;
+    });
+  },
+
+  toArray: function() {
+    return this.split('');
+  },
+
+  succ: function() {
+    return this.slice(0, this.length - 1) +
+      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
+  },
+
+  times: function(count) {
+    return count < 1 ? '' : new Array(count + 1).join(this);
+  },
+
+  camelize: function() {
+    var parts = this.split('-'), len = parts.length;
+    if (len == 1) return parts[0];
+
+    var camelized = this.charAt(0) == '-'
+      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
+      : parts[0];
+
+    for (var i = 1; i < len; i++)
+      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
+
+    return camelized;
+  },
+
+  capitalize: function() {
+    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
+  },
+
+  underscore: function() {
+    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
+  },
+
+  dasherize: function() {
+    return this.gsub(/_/,'-');
+  },
+
+  inspect: function(useDoubleQuotes) {
+    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
+      var character = String.specialChar[match[0]];
+      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
+    });
+    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
+    return "'" + escapedString.replace(/'/g, '\\\'') + "'";
+  },
+
+  toJSON: function() {
+    return this.inspect(true);
+  },
+
+  unfilterJSON: function(filter) {
+    return this.sub(filter || Prototype.JSONFilter, '#{1}');
+  },
+
+  isJSON: function() {
+    var str = this;
+    if (str.blank()) return false;
+    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
+    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
+  },
+
+  evalJSON: function(sanitize) {
+    var json = this.unfilterJSON();
+    try {
+      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
+    } catch (e) { }
+    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
+  },
+
+  include: function(pattern) {
+    return this.indexOf(pattern) > -1;
+  },
+
+  startsWith: function(pattern) {
+    return this.indexOf(pattern) === 0;
+  },
+
+  endsWith: function(pattern) {
+    var d = this.length - pattern.length;
+    return d >= 0 && this.lastIndexOf(pattern) === d;
+  },
+
+  empty: function() {
+    return this == '';
+  },
+
+  blank: function() {
+    return /^\s*$/.test(this);
+  },
+
+  interpolate: function(object, pattern) {
+    return new Template(this, pattern).evaluate(object);
+  }
+});
+
+if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
+  escapeHTML: function() {
+    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
+  },
+  unescapeHTML: function() {
+    return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
+  }
+});
+
+String.prototype.gsub.prepareReplacement = function(replacement) {
+  if (Object.isFunction(replacement)) return replacement;
+  var template = new Template(replacement);
+  return function(match) { return template.evaluate(match) };
+};
+
+String.prototype.parseQuery = String.prototype.toQueryParams;
+
+Object.extend(String.prototype.escapeHTML, {
+  div:  document.createElement('div'),
+  text: document.createTextNode('')
+});
+
+with (String.prototype.escapeHTML) div.appendChild(text);
+
+var Template = Class.create({
+  initialize: function(template, pattern) {
+    this.template = template.toString();
+    this.pattern = pattern || Template.Pattern;
+  },
+
+  evaluate: function(object) {
+    if (Object.isFunction(object.toTemplateReplacements))
+      object = object.toTemplateReplacements();
+
+    return this.template.gsub(this.pattern, function(match) {
+      if (object == null) return '';
+
+      var before = match[1] || '';
+      if (before == '\\') return match[2];
+
+      var ctx = object, expr = match[3];
+      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
+      match = pattern.exec(expr);
+      if (match == null) return before;
+
+      while (match != null) {
+        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
+        ctx = ctx[comp];
+        if (null == ctx || '' == match[3]) break;
+        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
+        match = pattern.exec(expr);
+      }
+
+      return before + String.interpret(ctx);
+    });
+  }
+});
+Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
+
+var $break = { };
+
+var Enumerable = {
+  each: function(iterator, context) {
+    var index = 0;
+    iterator = iterator.bind(context);
+    try {
+      this._each(function(value) {
+        iterator(value, index++);
+      });
+    } catch (e) {
+      if (e != $break) throw e;
+    }
+    return this;
+  },
+
+  eachSlice: function(number, iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var index = -number, slices = [], array = this.toArray();
+    while ((index += number) < array.length)
+      slices.push(array.slice(index, index+number));
+    return slices.collect(iterator, context);
+  },
+
+  all: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result = true;
+    this.each(function(value, index) {
+      result = result && !!iterator(value, index);
+      if (!result) throw $break;
+    });
+    return result;
+  },
+
+  any: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result = false;
+    this.each(function(value, index) {
+      if (result = !!iterator(value, index))
+        throw $break;
+    });
+    return result;
+  },
+
+  collect: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var results = [];
+    this.each(function(value, index) {
+      results.push(iterator(value, index));
+    });
+    return results;
+  },
+
+  detect: function(iterator, context) {
+    iterator = iterator.bind(context);
+    var result;
+    this.each(function(value, index) {
+      if (iterator(value, index)) {
+        result = value;
+        throw $break;
+      }
+    });
+    return result;
+  },
+
+  findAll: function(iterator, context) {
+    iterator = iterator.bind(context);
+    var results = [];
+    this.each(function(value, index) {
+      if (iterator(value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  grep: function(filter, iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var results = [];
+
+    if (Object.isString(filter))
+      filter = new RegExp(filter);
+
+    this.each(function(value, index) {
+      if (filter.match(value))
+        results.push(iterator(value, index));
+    });
+    return results;
+  },
+
+  include: function(object) {
+    if (Object.isFunction(this.indexOf))
+      if (this.indexOf(object) != -1) return true;
+
+    var found = false;
+    this.each(function(value) {
+      if (value == object) {
+        found = true;
+        throw $break;
+      }
+    });
+    return found;
+  },
+
+  inGroupsOf: function(number, fillWith) {
+    fillWith = Object.isUndefined(fillWith) ? null : fillWith;
+    return this.eachSlice(number, function(slice) {
+      while(slice.length < number) slice.push(fillWith);
+      return slice;
+    });
+  },
+
+  inject: function(memo, iterator, context) {
+    iterator = iterator.bind(context);
+    this.each(function(value, index) {
+      memo = iterator(memo, value, index);
+    });
+    return memo;
+  },
+
+  invoke: function(method) {
+    var args = $A(arguments).slice(1);
+    return this.map(function(value) {
+      return value[method].apply(value, args);
+    });
+  },
+
+  max: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result;
+    this.each(function(value, index) {
+      value = iterator(value, index);
+      if (result == null || value >= result)
+        result = value;
+    });
+    return result;
+  },
+
+  min: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var result;
+    this.each(function(value, index) {
+      value = iterator(value, index);
+      if (result == null || value < result)
+        result = value;
+    });
+    return result;
+  },
+
+  partition: function(iterator, context) {
+    iterator = iterator ? iterator.bind(context) : Prototype.K;
+    var trues = [], falses = [];
+    this.each(function(value, index) {
+      (iterator(value, index) ?
+        trues : falses).push(value);
+    });
+    return [trues, falses];
+  },
+
+  pluck: function(property) {
+    var results = [];
+    this.each(function(value) {
+      results.push(value[property]);
+    });
+    return results;
+  },
+
+  reject: function(iterator, context) {
+    iterator = iterator.bind(context);
+    var results = [];
+    this.each(function(value, index) {
+      if (!iterator(value, index))
+        results.push(value);
+    });
+    return results;
+  },
+
+  sortBy: function(iterator, context) {
+    iterator = iterator.bind(context);
+    return this.map(function(value, index) {
+      return {value: value, criteria: iterator(value, index)};
+    }).sort(function(left, right) {
+      var a = left.criteria, b = right.criteria;
+      return a < b ? -1 : a > b ? 1 : 0;
+    }).pluck('value');
+  },
+
+  toArray: function() {
+    return this.map();
+  },
+
+  zip: function() {
+    var iterator = Prototype.K, args = $A(arguments);
+    if (Object.isFunction(args.last()))
+      iterator = args.pop();
+
+    var collections = [this].concat(args).map($A);
+    return this.map(function(value, index) {
+      return iterator(collections.pluck(index));
+    });
+  },
+
+  size: function() {
+    return this.toArray().length;
+  },
+
+  inspect: function() {
+    return '#<Enumerable:' + this.toArray().inspect() + '>';
+  }
+};
+
+Object.extend(Enumerable, {
+  map:     Enumerable.collect,
+  find:    Enumerable.detect,
+  select:  Enumerable.findAll,
+  filter:  Enumerable.findAll,
+  member:  Enumerable.include,
+  entries: Enumerable.toArray,
+  every:   Enumerable.all,
+  some:    Enumerable.any
+});
+function $A(iterable) {
+  if (!iterable) return [];
+  if (iterable.toArray) return iterable.toArray();
+  var length = iterable.length || 0, results = new Array(length);
+  while (length--) results[length] = iterable[length];
+  return results;
+}
+
+if (Prototype.Browser.WebKit) {
+  $A = function(iterable) {
+    if (!iterable) return [];
+    if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') &&
+        iterable.toArray) return iterable.toArray();
+    var length = iterable.length || 0, results = new Array(length);
+    while (length--) results[length] = iterable[length];
+    return results;
+  };
+}
+
+Array.from = $A;
+
+Object.extend(Array.prototype, Enumerable);
+
+if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;
+
+Object.extend(Array.prototype, {
+  _each: function(iterator) {
+    for (var i = 0, length = this.length; i < length; i++)
+      iterator(this[i]);
+  },
+
+  clear: function() {
+    this.length = 0;
+    return this;
+  },
+
+  first: function() {
+    return this[0];
+  },
+
+  last: function() {
+    return this[this.length - 1];
+  },
+
+  compact: function() {
+    return this.select(function(value) {
+      return value != null;
+    });
+  },
+
+  flatten: function() {
+    return this.inject([], function(array, value) {
+      return array.concat(Object.isArray(value) ?
+        value.flatten() : [value]);
+    });
+  },
+
+  without: function() {
+    var values = $A(arguments);
+    return this.select(function(value) {
+      return !values.include(value);
+    });
+  },
+
+  reverse: function(inline) {
+    return (inline !== false ? this : this.toArray())._reverse();
+  },
+
+  reduce: function() {
+    return this.length > 1 ? this : this[0];
+  },
+
+  uniq: function(sorted) {
+    return this.inject([], function(array, value, index) {
+      if (0 == index || (sorted ? array.last() != value : !array.include(value)))
+        array.push(value);
+      return array;
+    });
+  },
+
+  intersect: function(array) {
+    return this.uniq().findAll(function(item) {
+      return array.detect(function(value) { return item === value });
+    });
+  },
+
+  clone: function() {
+    return [].concat(this);
+  },
+
+  size: function() {
+    return this.length;
+  },
+
+  inspect: function() {
+    return '[' + this.map(Object.inspect).join(', ') + ']';
+  },
+
+  toJSON: function() {
+    var results = [];
+    this.each(function(object) {
+      var value = Object.toJSON(object);
+      if (!Object.isUndefined(value)) results.push(value);
+    });
+    return '[' + results.join(', ') + ']';
+  }
+});
+
+// use native browser JS 1.6 implementation if available
+if (Object.isFunction(Array.prototype.forEach))
+  Array.prototype._each = Array.prototype.forEach;
+
+if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
+  i || (i = 0);
+  var length = this.length;
+  if (i < 0) i = length + i;
+  for (; i < length; i++)
+    if (this[i] === item) return i;
+  return -1;
+};
+
+if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
+  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
+  var n = this.slice(0, i).reverse().indexOf(item);
+  return (n < 0) ? n : i - n - 1;
+};
+
+Array.prototype.toArray = Array.prototype.clone;
+
+function $w(string) {
+  if (!Object.isString(string)) return [];
+  string = string.strip();
+  return string ? string.split(/\s+/) : [];
+}
+
+if (Prototype.Browser.Opera){
+  Array.prototype.concat = function() {
+    var array = [];
+    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
+    for (var i = 0, length = arguments.length; i < length; i++) {
+      if (Object.isArray(arguments[i])) {
+        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
+          array.push(arguments[i][j]);
+      } else {
+        array.push(arguments[i]);
+      }
+    }
+    return array;
+  };
+}
+Object.extend(Number.prototype, {
+  toColorPart: function() {
+    return this.toPaddedString(2, 16);
+  },
+
+  succ: function() {
+    return this + 1;
+  },
+
+  times: function(iterator) {
+    $R(0, this, true).each(iterator);
+    return this;
+  },
+
+  toPaddedString: function(length, radix) {
+    var string = this.toString(radix || 10);
+    return '0'.times(length - string.length) + string;
+  },
+
+  toJSON: function() {
+    return isFinite(this) ? this.toString() : 'null';
+  }
+});
+
+$w('abs round ceil floor').each(function(method){
+  Number.prototype[method] = Math[method].methodize();
+});
+function $H(object) {
+  return new Hash(object);
+};
+
+var Hash = Class.create(Enumerable, (function() {
+
+  function toQueryPair(key, value) {
+    if (Object.isUndefined(value)) return key;
+    return key + '=' + encodeURIComponent(String.interpret(value));
+  }
+
+  return {
+    initialize: function(object) {
+      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
+    },
+
+    _each: function(iterator) {
+      for (var key in this._object) {
+        var value = this._object[key], pair = [key, value];
+        pair.key = key;
+        pair.value = value;
+        iterator(pair);
+      }
+    },
+
+    set: function(key, value) {
+      return this._object[key] = value;
+    },
+
+    get: function(key) {
+      return this._object[key];
+    },
+
+    unset: function(key) {
+      var value = this._object[key];
+      delete this._object[key];
+      return value;
+    },
+
+    toObject: function() {
+      return Object.clone(this._object);
+    },
+
+    keys: function() {
+      return this.pluck('key');
+    },
+
+    values: function() {
+      return this.pluck('value');
+    },
+
+    index: function(value) {
+      var match = this.detect(function(pair) {
+        return pair.value === value;
+      });
+      return match && match.key;
+    },
+
+    merge: function(object) {
+      return this.clone().update(object);
+    },
+
+    update: function(object) {
+      return new Hash(object).inject(this, function(result, pair) {
+        result.set(pair.key, pair.value);
+        return result;
+      });
+    },
+
+    toQueryString: function() {
+      return this.map(function(pair) {
+        var key = encodeURIComponent(pair.key), values = pair.value;
+
+        if (values && typeof values == 'object') {
+          if (Object.isArray(values))
+            return values.map(toQueryPair.curry(key)).join('&');
+        }
+        return toQueryPair(key, values);
+      }).join('&');
+    },
+
+    inspect: function() {
+      return '#<Hash:{' + this.map(function(pair) {
+        return pair.map(Object.inspect).join(': ');
+      }).join(', ') + '}>';
+    },
+
+    toJSON: function() {
+      return Object.toJSON(this.toObject());
+    },
+
+    clone: function() {
+      return new Hash(this);
+    }
+  }
+})());
+
+Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
+Hash.from = $H;
+var ObjectRange = Class.create(Enumerable, {
+  initialize: function(start, end, exclusive) {
+    this.start = start;
+    this.end = end;
+    this.exclusive = exclusive;
+  },
+
+  _each: function(iterator) {
+    var value = this.start;
+    while (this.include(value)) {
+      iterator(value);
+      value = value.succ();
+    }
+  },
+
+  include: function(value) {
+    if (value < this.start)
+      return false;
+    if (this.exclusive)
+      return value < this.end;
+    return value <= this.end;
+  }
+});
+
+var $R = function(start, end, exclusive) {
+  return new ObjectRange(start, end, exclusive);
+};
+
+var Ajax = {
+  getTransport: function() {
+    return Try.these(
+      function() {return new XMLHttpRequest()},
+      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
+      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
+    ) || false;
+  },
+
+  activeRequestCount: 0
+};
+
+Ajax.Responders = {
+  responders: [],
+
+  _each: function(iterator) {
+    this.responders._each(iterator);
+  },
+
+  register: function(responder) {
+    if (!this.include(responder))
+      this.responders.push(responder);
+  },
+
+  unregister: function(responder) {
+    this.responders = this.responders.without(responder);
+  },
+
+  dispatch: function(callback, request, transport, json) {
+    this.each(function(responder) {
+      if (Object.isFunction(responder[callback])) {
+        try {
+          responder[callback].apply(responder, [request, transport, json]);
+        } catch (e) { }
+      }
+    });
+  }
+};
+
+Object.extend(Ajax.Responders, Enumerable);
+
+Ajax.Responders.register({
+  onCreate:   function() { Ajax.activeRequestCount++ },
+  onComplete: function() { Ajax.activeRequestCount-- }
+});
+
+Ajax.Base = Class.create({
+  initialize: function(options) {
+    this.options = {
+      method:       'post',
+      asynchronous: true,
+      contentType:  'application/x-www-form-urlencoded',
+      encoding:     'UTF-8',
+      parameters:   '',
+      evalJSON:     true,
+      evalJS:       true
+    };
+    Object.extend(this.options, options || { });
+
+    this.options.method = this.options.method.toLowerCase();
+
+    if (Object.isString(this.options.parameters))
+      this.options.parameters = this.options.parameters.toQueryParams();
+    else if (Object.isHash(this.options.parameters))
+      this.options.parameters = this.options.parameters.toObject();
+  }
+});
+
+Ajax.Request = Class.create(Ajax.Base, {
+  _complete: false,
+
+  initialize: function($super, url, options) {
+    $super(options);
+    this.transport = Ajax.getTransport();
+    this.request(url);
+  },
+
+  request: function(url) {
+    this.url = url;
+    this.method = this.options.method;
+    var params = Object.clone(this.options.parameters);
+
+    if (!['get', 'post'].include(this.method)) {
+      // simulate other verbs over post
+      params['_method'] = this.method;
+      this.method = 'post';
+    }
+
+    this.parameters = params;
+
+    if (params = Object.toQueryString(params)) {
+      // when GET, append parameters to URL
+      if (this.method == 'get')
+        this.url += (this.url.include('?') ? '&' : '?') + params;
+      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
+        params += '&_=';
+    }
+
+    try {
+      var response = new Ajax.Response(this);
+      if (this.options.onCreate) this.options.onCreate(response);
+      Ajax.Responders.dispatch('onCreate', this, response);
+
+      this.transport.open(this.method.toUpperCase(), this.url,
+        this.options.asynchronous);
+
+      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
+
+      this.transport.onreadystatechange = this.onStateChange.bind(this);
+      this.setRequestHeaders();
+
+      this.body = this.method == 'post' ? (this.options.postBody || params) : null;
+      this.transport.send(this.body);
+
+      /* Force Firefox to handle ready state 4 for synchronous requests */
+      if (!this.options.asynchronous && this.transport.overrideMimeType)
+        this.onStateChange();
+
+    }
+    catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  onStateChange: function() {
+    var readyState = this.transport.readyState;
+    if (readyState > 1 && !((readyState == 4) && this._complete))
+      this.respondToReadyState(this.transport.readyState);
+  },
+
+  setRequestHeaders: function() {
+    var headers = {
+      'X-Requested-With': 'XMLHttpRequest',
+      'X-Prototype-Version': Prototype.Version,
+      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
+    };
+
+    if (this.method == 'post') {
+      headers['Content-type'] = this.options.contentType +
+        (this.options.encoding ? '; charset=' + this.options.encoding : '');
+
+      /* Force "Connection: close" for older Mozilla browsers to work
+       * around a bug where XMLHttpRequest sends an incorrect
+       * Content-length header. See Mozilla Bugzilla #246651.
+       */
+      if (this.transport.overrideMimeType &&
+          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
+            headers['Connection'] = 'close';
+    }
+
+    // user-defined headers
+    if (typeof this.options.requestHeaders == 'object') {
+      var extras = this.options.requestHeaders;
+
+      if (Object.isFunction(extras.push))
+        for (var i = 0, length = extras.length; i < length; i += 2)
+          headers[extras[i]] = extras[i+1];
+      else
+        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
+    }
+
+    for (var name in headers)
+      this.transport.setRequestHeader(name, headers[name]);
+  },
+
+  success: function() {
+    var status = this.getStatus();
+    return !status || (status >= 200 && status < 300);
+  },
+
+  getStatus: function() {
+    try {
+      return this.transport.status || 0;
+    } catch (e) { return 0 }
+  },
+
+  respondToReadyState: function(readyState) {
+    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
+
+    if (state == 'Complete') {
+      try {
+        this._complete = true;
+        (this.options['on' + response.status]
+         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
+         || Prototype.emptyFunction)(response, response.headerJSON);
+      } catch (e) {
+        this.dispatchException(e);
+      }
+
+      var contentType = response.getHeader('Content-type');
+      if (this.options.evalJS == 'force'
+          || (this.options.evalJS && this.isSameOrigin() && contentType
+          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
+        this.evalResponse();
+    }
+
+    try {
+      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
+      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
+    } catch (e) {
+      this.dispatchException(e);
+    }
+
+    if (state == 'Complete') {
+      // avoid memory leak in MSIE: clean up
+      this.transport.onreadystatechange = Prototype.emptyFunction;
+    }
+  },
+
+  isSameOrigin: function() {
+    var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
+    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
+      protocol: location.protocol,
+      domain: document.domain,
+      port: location.port ? ':' + location.port : ''
+    }));
+  },
+
+  getHeader: function(name) {
+    try {
+      return this.transport.getResponseHeader(name) || null;
+    } catch (e) { return null }
+  },
+
+  evalResponse: function() {
+    try {
+      return eval((this.transport.responseText || '').unfilterJSON());
+    } catch (e) {
+      this.dispatchException(e);
+    }
+  },
+
+  dispatchException: function(exception) {
+    (this.options.onException || Prototype.emptyFunction)(this, exception);
+    Ajax.Responders.dispatch('onException', this, exception);
+  }
+});
+
+Ajax.Request.Events =
+  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+Ajax.Response = Class.create({
+  initialize: function(request){
+    this.request = request;
+    var transport  = this.transport  = request.transport,
+        readyState = this.readyState = transport.readyState;
+
+    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
+      this.status       = this.getStatus();
+      this.statusText   = this.getStatusText();
+      this.responseText = String.interpret(transport.responseText);
+      this.headerJSON   = this._getHeaderJSON();
+    }
+
+    if(readyState == 4) {
+      var xml = transport.responseXML;
+      this.responseXML  = Object.isUndefined(xml) ? null : xml;
+      this.responseJSON = this._getResponseJSON();
+    }
+  },
+
+  status:      0,
+  statusText: '',
+
+  getStatus: Ajax.Request.prototype.getStatus,
+
+  getStatusText: function() {
+    try {
+      return this.transport.statusText || '';
+    } catch (e) { return '' }
+  },
+
+  getHeader: Ajax.Request.prototype.getHeader,
+
+  getAllHeaders: function() {
+    try {
+      return this.getAllResponseHeaders();
+    } catch (e) { return null }
+  },
+
+  getResponseHeader: function(name) {
+    return this.transport.getResponseHeader(name);
+  },
+
+  getAllResponseHeaders: function() {
+    return this.transport.getAllResponseHeaders();
+  },
+
+  _getHeaderJSON: function() {
+    var json = this.getHeader('X-JSON');
+    if (!json) return null;
+    json = decodeURIComponent(escape(json));
+    try {
+      return json.evalJSON(this.request.options.sanitizeJSON ||
+        !this.request.isSameOrigin());
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  },
+
+  _getResponseJSON: function() {
+    var options = this.request.options;
+    if (!options.evalJSON || (options.evalJSON != 'force' &&
+      !(this.getHeader('Content-type') || '').include('application/json')) ||
+        this.responseText.blank())
+          return null;
+    try {
+      return this.responseText.evalJSON(options.sanitizeJSON ||
+        !this.request.isSameOrigin());
+    } catch (e) {
+      this.request.dispatchException(e);
+    }
+  }
+});
+
+Ajax.Updater = Class.create(Ajax.Request, {
+  initialize: function($super, container, url, options) {
+    this.container = {
+      success: (container.success || container),
+      failure: (container.failure || (container.success ? null : container))
+    };
+
+    options = Object.clone(options);
+    var onComplete = options.onComplete;
+    options.onComplete = (function(response, json) {
+      this.updateContent(response.responseText);
+      if (Object.isFunction(onComplete)) onComplete(response, json);
+    }).bind(this);
+
+    $super(url, options);
+  },
+
+  updateContent: function(responseText) {
+    var receiver = this.container[this.success() ? 'success' : 'failure'],
+        options = this.options;
+
+    if (!options.evalScripts) responseText = responseText.stripScripts();
+
+    if (receiver = $(receiver)) {
+      if (options.insertion) {
+        if (Object.isString(options.insertion)) {
+          var insertion = { }; insertion[options.insertion] = responseText;
+          receiver.insert(insertion);
+        }
+        else options.insertion(receiver, responseText);
+      }
+      else receiver.update(responseText);
+    }
+  }
+});
+
+Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
+  initialize: function($super, container, url, options) {
+    $super(options);
+    this.onComplete = this.options.onComplete;
+
+    this.frequency = (this.options.frequency || 2);
+    this.decay = (this.options.decay || 1);
+
+    this.updater = { };
+    this.container = container;
+    this.url = url;
+
+    this.start();
+  },
+
+  start: function() {
+    this.options.onComplete = this.updateComplete.bind(this);
+    this.onTimerEvent();
+  },
+
+  stop: function() {
+    this.updater.options.onComplete = undefined;
+    clearTimeout(this.timer);
+    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
+  },
+
+  updateComplete: function(response) {
+    if (this.options.decay) {
+      this.decay = (response.responseText == this.lastText ?
+        this.decay * this.options.decay : 1);
+
+      this.lastText = response.responseText;
+    }
+    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
+  },
+
+  onTimerEvent: function() {
+    this.updater = new Ajax.Updater(this.container, this.url, this.options);
+  }
+});
+function $(element) {
+  if (arguments.length > 1) {
+    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
+      elements.push($(arguments[i]));
+    return elements;
+  }
+  if (Object.isString(element))
+    element = document.getElementById(element);
+  return Element.extend(element);
+}
+
+if (Prototype.BrowserFeatures.XPath) {
+  document._getElementsByXPath = function(expression, parentElement) {
+    var results = [];
+    var query = document.evaluate(expression, $(parentElement) || document,
+      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+    for (var i = 0, length = query.snapshotLength; i < length; i++)
+      results.push(Element.extend(query.snapshotItem(i)));
+    return results;
+  };
+}
+
+/*--------------------------------------------------------------------------*/
+
+if (!window.Node) var Node = { };
+
+if (!Node.ELEMENT_NODE) {
+  // DOM level 2 ECMAScript Language Binding
+  Object.extend(Node, {
+    ELEMENT_NODE: 1,
+    ATTRIBUTE_NODE: 2,
+    TEXT_NODE: 3,
+    CDATA_SECTION_NODE: 4,
+    ENTITY_REFERENCE_NODE: 5,
+    ENTITY_NODE: 6,
+    PROCESSING_INSTRUCTION_NODE: 7,
+    COMMENT_NODE: 8,
+    DOCUMENT_NODE: 9,
+    DOCUMENT_TYPE_NODE: 10,
+    DOCUMENT_FRAGMENT_NODE: 11,
+    NOTATION_NODE: 12
+  });
+}
+
+(function() {
+  var element = this.Element;
+  this.Element = function(tagName, attributes) {
+    attributes = attributes || { };
+    tagName = tagName.toLowerCase();
+    var cache = Element.cache;
+    if (Prototype.Browser.IE && attributes.name) {
+      tagName = '<' + tagName + ' name="' + attributes.name + '">';
+      delete attributes.name;
+      return Element.writeAttribute(document.createElement(tagName), attributes);
+    }
+    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
+    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
+  };
+  Object.extend(this.Element, element || { });
+}).call(window);
+
+Element.cache = { };
+
+Element.Methods = {
+  visible: function(element) {
+    return $(element).style.display != 'none';
+  },
+
+  toggle: function(element) {
+    element = $(element);
+    Element[Element.visible(element) ? 'hide' : 'show'](element);
+    return element;
+  },
+
+  hide: function(element) {
+    $(element).style.display = 'none';
+    return element;
+  },
+
+  show: function(element) {
+    $(element).style.display = '';
+    return element;
+  },
+
+  remove: function(element) {
+    element = $(element);
+    element.parentNode.removeChild(element);
+    return element;
+  },
+
+  update: function(element, content) {
+    element = $(element);
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) return element.update().insert(content);
+    content = Object.toHTML(content);
+    element.innerHTML = content.stripScripts();
+    content.evalScripts.bind(content).defer();
+    return element;
+  },
+
+  replace: function(element, content) {
+    element = $(element);
+    if (content && content.toElement) content = content.toElement();
+    else if (!Object.isElement(content)) {
+      content = Object.toHTML(content);
+      var range = element.ownerDocument.createRange();
+      range.selectNode(element);
+      content.evalScripts.bind(content).defer();
+      content = range.createContextualFragment(content.stripScripts());
+    }
+    element.parentNode.replaceChild(content, element);
+    return element;
+  },
+
+  insert: function(element, insertions) {
+    element = $(element);
+
+    if (Object.isString(insertions) || Object.isNumber(insertions) ||
+        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
+          insertions = {bottom:insertions};
+
+    var content, insert, tagName, childNodes;
+
+    for (var position in insertions) {
+      content  = insertions[position];
+      position = position.toLowerCase();
+      insert = Element._insertionTranslations[position];
+
+      if (content && content.toElement) content = content.toElement();
+      if (Object.isElement(content)) {
+        insert(element, content);
+        continue;
+      }
+
+      content = Object.toHTML(content);
+
+      tagName = ((position == 'before' || position == 'after')
+        ? element.parentNode : element).tagName.toUpperCase();
+
+      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+
+      if (position == 'top' || position == 'after') childNodes.reverse();
+      childNodes.each(insert.curry(element));
+
+      content.evalScripts.bind(content).defer();
+    }
+
+    return element;
+  },
+
+  wrap: function(element, wrapper, attributes) {
+    element = $(element);
+    if (Object.isElement(wrapper))
+      $(wrapper).writeAttribute(attributes || { });
+    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
+    else wrapper = new Element('div', wrapper);
+    if (element.parentNode)
+      element.parentNode.replaceChild(wrapper, element);
+    wrapper.appendChild(element);
+    return wrapper;
+  },
+
+  inspect: function(element) {
+    element = $(element);
+    var result = '<' + element.tagName.toLowerCase();
+    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
+      var property = pair.first(), attribute = pair.last();
+      var value = (element[property] || '').toString();
+      if (value) result += ' ' + attribute + '=' + value.inspect(true);
+    });
+    return result + '>';
+  },
+
+  recursivelyCollect: function(element, property) {
+    element = $(element);
+    var elements = [];
+    while (element = element[property])
+      if (element.nodeType == 1)
+        elements.push(Element.extend(element));
+    return elements;
+  },
+
+  ancestors: function(element) {
+    return $(element).recursivelyCollect('parentNode');
+  },
+
+  descendants: function(element) {
+    return $(element).select("*");
+  },
+
+  firstDescendant: function(element) {
+    element = $(element).firstChild;
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    return $(element);
+  },
+
+  immediateDescendants: function(element) {
+    if (!(element = $(element).firstChild)) return [];
+    while (element && element.nodeType != 1) element = element.nextSibling;
+    if (element) return [element].concat($(element).nextSiblings());
+    return [];
+  },
+
+  previousSiblings: function(element) {
+    return $(element).recursivelyCollect('previousSibling');
+  },
+
+  nextSiblings: function(element) {
+    return $(element).recursivelyCollect('nextSibling');
+  },
+
+  siblings: function(element) {
+    element = $(element);
+    return element.previousSiblings().reverse().concat(element.nextSiblings());
+  },
+
+  match: function(element, selector) {
+    if (Object.isString(selector))
+      selector = new Selector(selector);
+    return selector.match($(element));
+  },
+
+  up: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(element.parentNode);
+    var ancestors = element.ancestors();
+    return Object.isNumber(expression) ? ancestors[expression] :
+      Selector.findElement(ancestors, expression, index);
+  },
+
+  down: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return element.firstDescendant();
+    return Object.isNumber(expression) ? element.descendants()[expression] :
+      element.select(expression)[index || 0];
+  },
+
+  previous: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
+    var previousSiblings = element.previousSiblings();
+    return Object.isNumber(expression) ? previousSiblings[expression] :
+      Selector.findElement(previousSiblings, expression, index);
+  },
+
+  next: function(element, expression, index) {
+    element = $(element);
+    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
+    var nextSiblings = element.nextSiblings();
+    return Object.isNumber(expression) ? nextSiblings[expression] :
+      Selector.findElement(nextSiblings, expression, index);
+  },
+
+  select: function() {
+    var args = $A(arguments), element = $(args.shift());
+    return Selector.findChildElements(element, args);
+  },
+
+  adjacent: function() {
+    var args = $A(arguments), element = $(args.shift());
+    return Selector.findChildElements(element.parentNode, args).without(element);
+  },
+
+  identify: function(element) {
+    element = $(element);
+    var id = element.readAttribute('id'), self = arguments.callee;
+    if (id) return id;
+    do { id = 'anonymous_element_' + self.counter++ } while ($(id));
+    element.writeAttribute('id', id);
+    return id;
+  },
+
+  readAttribute: function(element, name) {
+    element = $(element);
+    if (Prototype.Browser.IE) {
+      var t = Element._attributeTranslations.read;
+      if (t.values[name]) return t.values[name](element, name);
+      if (t.names[name]) name = t.names[name];
+      if (name.include(':')) {
+        return (!element.attributes || !element.attributes[name]) ? null :
+         element.attributes[name].value;
+      }
+    }
+    return element.getAttribute(name);
+  },
+
+  writeAttribute: function(element, name, value) {
+    element = $(element);
+    var attributes = { }, t = Element._attributeTranslations.write;
+
+    if (typeof name == 'object') attributes = name;
+    else attributes[name] = Object.isUndefined(value) ? true : value;
+
+    for (var attr in attributes) {
+      name = t.names[attr] || attr;
+      value = attributes[attr];
+      if (t.values[attr]) name = t.values[attr](element, value);
+      if (value === false || value === null)
+        element.removeAttribute(name);
+      else if (value === true)
+        element.setAttribute(name, name);
+      else element.setAttribute(name, value);
+    }
+    return element;
+  },
+
+  getHeight: function(element) {
+    return $(element).getDimensions().height;
+  },
+
+  getWidth: function(element) {
+    return $(element).getDimensions().width;
+  },
+
+  classNames: function(element) {
+    return new Element.ClassNames(element);
+  },
+
+  hasClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    var elementClassName = element.className;
+    return (elementClassName.length > 0 && (elementClassName == className ||
+      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
+  },
+
+  addClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    if (!element.hasClassName(className))
+      element.className += (element.className ? ' ' : '') + className;
+    return element;
+  },
+
+  removeClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    element.className = element.className.replace(
+      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
+    return element;
+  },
+
+  toggleClassName: function(element, className) {
+    if (!(element = $(element))) return;
+    return element[element.hasClassName(className) ?
+      'removeClassName' : 'addClassName'](className);
+  },
+
+  // removes whitespace-only text node children
+  cleanWhitespace: function(element) {
+    element = $(element);
+    var node = element.firstChild;
+    while (node) {
+      var nextNode = node.nextSibling;
+      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
+        element.removeChild(node);
+      node = nextNode;
+    }
+    return element;
+  },
+
+  empty: function(element) {
+    return $(element).innerHTML.blank();
+  },
+
+  descendantOf: function(element, ancestor) {
+    element = $(element), ancestor = $(ancestor);
+    var originalAncestor = ancestor;
+
+    if (element.compareDocumentPosition)
+      return (element.compareDocumentPosition(ancestor) & 8) === 8;
+
+    if (element.sourceIndex && !Prototype.Browser.Opera) {
+      var e = element.sourceIndex, a = ancestor.sourceIndex,
+       nextAncestor = ancestor.nextSibling;
+      if (!nextAncestor) {
+        do { ancestor = ancestor.parentNode; }
+        while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode);
+      }
+      if (nextAncestor && nextAncestor.sourceIndex)
+       return (e > a && e < nextAncestor.sourceIndex);
+    }
+
+    while (element = element.parentNode)
+      if (element == originalAncestor) return true;
+    return false;
+  },
+
+  scrollTo: function(element) {
+    element = $(element);
+    var pos = element.cumulativeOffset();
+    window.scrollTo(pos[0], pos[1]);
+    return element;
+  },
+
+  getStyle: function(element, style) {
+    element = $(element);
+    style = style == 'float' ? 'cssFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value) {
+      var css = document.defaultView.getComputedStyle(element, null);
+      value = css ? css[style] : null;
+    }
+    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
+    return value == 'auto' ? null : value;
+  },
+
+  getOpacity: function(element) {
+    return $(element).getStyle('opacity');
+  },
+
+  setStyle: function(element, styles) {
+    element = $(element);
+    var elementStyle = element.style, match;
+    if (Object.isString(styles)) {
+      element.style.cssText += ';' + styles;
+      return styles.include('opacity') ?
+        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
+    }
+    for (var property in styles)
+      if (property == 'opacity') element.setOpacity(styles[property]);
+      else
+        elementStyle[(property == 'float' || property == 'cssFloat') ?
+          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
+            property] = styles[property];
+
+    return element;
+  },
+
+  setOpacity: function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+    return element;
+  },
+
+  getDimensions: function(element) {
+    element = $(element);
+    var display = $(element).getStyle('display');
+    if (display != 'none' && display != null) // Safari bug
+      return {width: element.offsetWidth, height: element.offsetHeight};
+
+    // All *Width and *Height properties give 0 on elements with display none,
+    // so enable the element temporarily
+    var els = element.style;
+    var originalVisibility = els.visibility;
+    var originalPosition = els.position;
+    var originalDisplay = els.display;
+    els.visibility = 'hidden';
+    els.position = 'absolute';
+    els.display = 'block';
+    var originalWidth = element.clientWidth;
+    var originalHeight = element.clientHeight;
+    els.display = originalDisplay;
+    els.position = originalPosition;
+    els.visibility = originalVisibility;
+    return {width: originalWidth, height: originalHeight};
+  },
+
+  makePositioned: function(element) {
+    element = $(element);
+    var pos = Element.getStyle(element, 'position');
+    if (pos == 'static' || !pos) {
+      element._madePositioned = true;
+      element.style.position = 'relative';
+      // Opera returns the offset relative to the positioning context, when an
+      // element is position relative but top and left have not been defined
+      if (window.opera) {
+        element.style.top = 0;
+        element.style.left = 0;
+      }
+    }
+    return element;
+  },
+
+  undoPositioned: function(element) {
+    element = $(element);
+    if (element._madePositioned) {
+      element._madePositioned = undefined;
+      element.style.position =
+        element.style.top =
+        element.style.left =
+        element.style.bottom =
+        element.style.right = '';
+    }
+    return element;
+  },
+
+  makeClipping: function(element) {
+    element = $(element);
+    if (element._overflow) return element;
+    element._overflow = Element.getStyle(element, 'overflow') || 'auto';
+    if (element._overflow !== 'hidden')
+      element.style.overflow = 'hidden';
+    return element;
+  },
+
+  undoClipping: function(element) {
+    element = $(element);
+    if (!element._overflow) return element;
+    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
+    element._overflow = null;
+    return element;
+  },
+
+  cumulativeOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  positionedOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      element = element.offsetParent;
+      if (element) {
+        if (element.tagName == 'BODY') break;
+        var p = Element.getStyle(element, 'position');
+        if (p !== 'static') break;
+      }
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  absolutize: function(element) {
+    element = $(element);
+    if (element.getStyle('position') == 'absolute') return;
+    // Position.prepare(); // To be done manually by Scripty when it needs it.
+
+    var offsets = element.positionedOffset();
+    var top     = offsets[1];
+    var left    = offsets[0];
+    var width   = element.clientWidth;
+    var height  = element.clientHeight;
+
+    element._originalLeft   = left - parseFloat(element.style.left  || 0);
+    element._originalTop    = top  - parseFloat(element.style.top || 0);
+    element._originalWidth  = element.style.width;
+    element._originalHeight = element.style.height;
+
+    element.style.position = 'absolute';
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.width  = width + 'px';
+    element.style.height = height + 'px';
+    return element;
+  },
+
+  relativize: function(element) {
+    element = $(element);
+    if (element.getStyle('position') == 'relative') return;
+    // Position.prepare(); // To be done manually by Scripty when it needs it.
+
+    element.style.position = 'relative';
+    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
+    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
+
+    element.style.top    = top + 'px';
+    element.style.left   = left + 'px';
+    element.style.height = element._originalHeight;
+    element.style.width  = element._originalWidth;
+    return element;
+  },
+
+  cumulativeScrollOffset: function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.scrollTop  || 0;
+      valueL += element.scrollLeft || 0;
+      element = element.parentNode;
+    } while (element);
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  getOffsetParent: function(element) {
+    if (element.offsetParent) return $(element.offsetParent);
+    if (element == document.body) return $(element);
+
+    while ((element = element.parentNode) && element != document.body)
+      if (Element.getStyle(element, 'position') != 'static')
+        return $(element);
+
+    return $(document.body);
+  },
+
+  viewportOffset: function(forElement) {
+    var valueT = 0, valueL = 0;
+
+    var element = forElement;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+
+      // Safari fix
+      if (element.offsetParent == document.body &&
+        Element.getStyle(element, 'position') == 'absolute') break;
+
+    } while (element = element.offsetParent);
+
+    element = forElement;
+    do {
+      if (!Prototype.Browser.Opera || element.tagName == 'BODY') {
+        valueT -= element.scrollTop  || 0;
+        valueL -= element.scrollLeft || 0;
+      }
+    } while (element = element.parentNode);
+
+    return Element._returnOffset(valueL, valueT);
+  },
+
+  clonePosition: function(element, source) {
+    var options = Object.extend({
+      setLeft:    true,
+      setTop:     true,
+      setWidth:   true,
+      setHeight:  true,
+      offsetTop:  0,
+      offsetLeft: 0
+    }, arguments[2] || { });
+
+    // find page position of source
+    source = $(source);
+    var p = source.viewportOffset();
+
+    // find coordinate system to use
+    element = $(element);
+    var delta = [0, 0];
+    var parent = null;
+    // delta [0,0] will do fine with position: fixed elements,
+    // position:absolute needs offsetParent deltas
+    if (Element.getStyle(element, 'position') == 'absolute') {
+      parent = element.getOffsetParent();
+      delta = parent.viewportOffset();
+    }
+
+    // correct by body offsets (fixes Safari)
+    if (parent == document.body) {
+      delta[0] -= document.body.offsetLeft;
+      delta[1] -= document.body.offsetTop;
+    }
+
+    // set position
+    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
+    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
+    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
+    if (options.setHeight) element.style.height = source.offsetHeight + 'px';
+    return element;
+  }
+};
+
+Element.Methods.identify.counter = 1;
+
+Object.extend(Element.Methods, {
+  getElementsBySelector: Element.Methods.select,
+  childElements: Element.Methods.immediateDescendants
+});
+
+Element._attributeTranslations = {
+  write: {
+    names: {
+      className: 'class',
+      htmlFor:   'for'
+    },
+    values: { }
+  }
+};
+
+if (Prototype.Browser.Opera) {
+  Element.Methods.getStyle = Element.Methods.getStyle.wrap(
+    function(proceed, element, style) {
+      switch (style) {
+        case 'left': case 'top': case 'right': case 'bottom':
+          if (proceed(element, 'position') === 'static') return null;
+        case 'height': case 'width':
+          // returns '0px' for hidden elements; we want it to return null
+          if (!Element.visible(element)) return null;
+
+          // returns the border-box dimensions rather than the content-box
+          // dimensions, so we subtract padding and borders from the value
+          var dim = parseInt(proceed(element, style), 10);
+
+          if (dim !== element['offset' + style.capitalize()])
+            return dim + 'px';
+
+          var properties;
+          if (style === 'height') {
+            properties = ['border-top-width', 'padding-top',
+             'padding-bottom', 'border-bottom-width'];
+          }
+          else {
+            properties = ['border-left-width', 'padding-left',
+             'padding-right', 'border-right-width'];
+          }
+          return properties.inject(dim, function(memo, property) {
+            var val = proceed(element, property);
+            return val === null ? memo : memo - parseInt(val, 10);
+          }) + 'px';
+        default: return proceed(element, style);
+      }
+    }
+  );
+
+  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
+    function(proceed, element, attribute) {
+      if (attribute === 'title') return element.title;
+      return proceed(element, attribute);
+    }
+  );
+}
+
+else if (Prototype.Browser.IE) {
+  // IE doesn't report offsets correctly for static elements, so we change them
+  // to "relative" to get the values, then change them back.
+  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
+    function(proceed, element) {
+      element = $(element);
+      var position = element.getStyle('position');
+      if (position !== 'static') return proceed(element);
+      element.setStyle({ position: 'relative' });
+      var value = proceed(element);
+      element.setStyle({ position: position });
+      return value;
+    }
+  );
+
+  $w('positionedOffset viewportOffset').each(function(method) {
+    Element.Methods[method] = Element.Methods[method].wrap(
+      function(proceed, element) {
+        element = $(element);
+        var position = element.getStyle('position');
+        if (position !== 'static') return proceed(element);
+        // Trigger hasLayout on the offset parent so that IE6 reports
+        // accurate offsetTop and offsetLeft values for position: fixed.
+        var offsetParent = element.getOffsetParent();
+        if (offsetParent && offsetParent.getStyle('position') === 'fixed')
+          offsetParent.setStyle({ zoom: 1 });
+        element.setStyle({ position: 'relative' });
+        var value = proceed(element);
+        element.setStyle({ position: position });
+        return value;
+      }
+    );
+  });
+
+  Element.Methods.getStyle = function(element, style) {
+    element = $(element);
+    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
+    var value = element.style[style];
+    if (!value && element.currentStyle) value = element.currentStyle[style];
+
+    if (style == 'opacity') {
+      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
+        if (value[1]) return parseFloat(value[1]) / 100;
+      return 1.0;
+    }
+
+    if (value == 'auto') {
+      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
+        return element['offset' + style.capitalize()] + 'px';
+      return null;
+    }
+    return value;
+  };
+
+  Element.Methods.setOpacity = function(element, value) {
+    function stripAlpha(filter){
+      return filter.replace(/alpha\([^\)]*\)/gi,'');
+    }
+    element = $(element);
+    var currentStyle = element.currentStyle;
+    if ((currentStyle && !currentStyle.hasLayout) ||
+      (!currentStyle && element.style.zoom == 'normal'))
+        element.style.zoom = 1;
+
+    var filter = element.getStyle('filter'), style = element.style;
+    if (value == 1 || value === '') {
+      (filter = stripAlpha(filter)) ?
+        style.filter = filter : style.removeAttribute('filter');
+      return element;
+    } else if (value < 0.00001) value = 0;
+    style.filter = stripAlpha(filter) +
+      'alpha(opacity=' + (value * 100) + ')';
+    return element;
+  };
+
+  Element._attributeTranslations = {
+    read: {
+      names: {
+        'class': 'className',
+        'for':   'htmlFor'
+      },
+      values: {
+        _getAttr: function(element, attribute) {
+          return element.getAttribute(attribute, 2);
+        },
+        _getAttrNode: function(element, attribute) {
+          var node = element.getAttributeNode(attribute);
+          return node ? node.value : "";
+        },
+        _getEv: function(element, attribute) {
+          attribute = element.getAttribute(attribute);
+          return attribute ? attribute.toString().slice(23, -2) : null;
+        },
+        _flag: function(element, attribute) {
+          return $(element).hasAttribute(attribute) ? attribute : null;
+        },
+        style: function(element) {
+          return element.style.cssText.toLowerCase();
+        },
+        title: function(element) {
+          return element.title;
+        }
+      }
+    }
+  };
+
+  Element._attributeTranslations.write = {
+    names: Object.extend({
+      cellpadding: 'cellPadding',
+      cellspacing: 'cellSpacing'
+    }, Element._attributeTranslations.read.names),
+    values: {
+      checked: function(element, value) {
+        element.checked = !!value;
+      },
+
+      style: function(element, value) {
+        element.style.cssText = value ? value : '';
+      }
+    }
+  };
+
+  Element._attributeTranslations.has = {};
+
+  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
+      'encType maxLength readOnly longDesc').each(function(attr) {
+    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
+    Element._attributeTranslations.has[attr.toLowerCase()] = attr;
+  });
+
+  (function(v) {
+    Object.extend(v, {
+      href:        v._getAttr,
+      src:         v._getAttr,
+      type:        v._getAttr,
+      action:      v._getAttrNode,
+      disabled:    v._flag,
+      checked:     v._flag,
+      readonly:    v._flag,
+      multiple:    v._flag,
+      onload:      v._getEv,
+      onunload:    v._getEv,
+      onclick:     v._getEv,
+      ondblclick:  v._getEv,
+      onmousedown: v._getEv,
+      onmouseup:   v._getEv,
+      onmouseover: v._getEv,
+      onmousemove: v._getEv,
+      onmouseout:  v._getEv,
+      onfocus:     v._getEv,
+      onblur:      v._getEv,
+      onkeypress:  v._getEv,
+      onkeydown:   v._getEv,
+      onkeyup:     v._getEv,
+      onsubmit:    v._getEv,
+      onreset:     v._getEv,
+      onselect:    v._getEv,
+      onchange:    v._getEv
+    });
+  })(Element._attributeTranslations.read.values);
+}
+
+else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1) ? 0.999999 :
+      (value === '') ? '' : (value < 0.00001) ? 0 : value;
+    return element;
+  };
+}
+
+else if (Prototype.Browser.WebKit) {
+  Element.Methods.setOpacity = function(element, value) {
+    element = $(element);
+    element.style.opacity = (value == 1 || value === '') ? '' :
+      (value < 0.00001) ? 0 : value;
+
+    if (value == 1)
+      if(element.tagName == 'IMG' && element.width) {
+        element.width++; element.width--;
+      } else try {
+        var n = document.createTextNode(' ');
+        element.appendChild(n);
+        element.removeChild(n);
+      } catch (e) { }
+
+    return element;
+  };
+
+  // Safari returns margins on body which is incorrect if the child is absolutely
+  // positioned.  For performance reasons, redefine Element#cumulativeOffset for
+  // KHTML/WebKit only.
+  Element.Methods.cumulativeOffset = function(element) {
+    var valueT = 0, valueL = 0;
+    do {
+      valueT += element.offsetTop  || 0;
+      valueL += element.offsetLeft || 0;
+      if (element.offsetParent == document.body)
+        if (Element.getStyle(element, 'position') == 'absolute') break;
+
+      element = element.offsetParent;
+    } while (element);
+
+    return Element._returnOffset(valueL, valueT);
+  };
+}
+
+if (Prototype.Browser.IE || Prototype.Browser.Opera) {
+  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
+  Element.Methods.update = function(element, content) {
+    element = $(element);
+
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) return element.update().insert(content);
+
+    content = Object.toHTML(content);
+    var tagName = element.tagName.toUpperCase();
+
+    if (tagName in Element._insertionTranslations.tags) {
+      $A(element.childNodes).each(function(node) { element.removeChild(node) });
+      Element._getContentFromAnonymousElement(tagName, content.stripScripts())
+        .each(function(node) { element.appendChild(node) });
+    }
+    else element.innerHTML = content.stripScripts();
+
+    content.evalScripts.bind(content).defer();
+    return element;
+  };
+}
+
+if ('outerHTML' in document.createElement('div')) {
+  Element.Methods.replace = function(element, content) {
+    element = $(element);
+
+    if (content && content.toElement) content = content.toElement();
+    if (Object.isElement(content)) {
+      element.parentNode.replaceChild(content, element);
+      return element;
+    }
+
+    content = Object.toHTML(content);
+    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
+
+    if (Element._insertionTranslations.tags[tagName]) {
+      var nextSibling = element.next();
+      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
+      parent.removeChild(element);
+      if (nextSibling)
+        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
+      else
+        fragments.each(function(node) { parent.appendChild(node) });
+    }
+    else element.outerHTML = content.stripScripts();
+
+    content.evalScripts.bind(content).defer();
+    return element;
+  };
+}
+
+Element._returnOffset = function(l, t) {
+  var result = [l, t];
+  result.left = l;
+  result.top = t;
+  return result;
+};
+
+Element._getContentFromAnonymousElement = function(tagName, html) {
+  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
+  if (t) {
+    div.innerHTML = t[0] + html + t[1];
+    t[2].times(function() { div = div.firstChild });
+  } else div.innerHTML = html;
+  return $A(div.childNodes);
+};
+
+Element._insertionTranslations = {
+  before: function(element, node) {
+    element.parentNode.insertBefore(node, element);
+  },
+  top: function(element, node) {
+    element.insertBefore(node, element.firstChild);
+  },
+  bottom: function(element, node) {
+    element.appendChild(node);
+  },
+  after: function(element, node) {
+    element.parentNode.insertBefore(node, element.nextSibling);
+  },
+  tags: {
+    TABLE:  ['<table>',                '</table>',                   1],
+    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
+    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
+    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
+    SELECT: ['<select>',               '</select>',                  1]
+  }
+};
+
+(function() {
+  Object.extend(this.tags, {
+    THEAD: this.tags.TBODY,
+    TFOOT: this.tags.TBODY,
+    TH:    this.tags.TD
+  });
+}).call(Element._insertionTranslations);
+
+Element.Methods.Simulated = {
+  hasAttribute: function(element, attribute) {
+    attribute = Element._attributeTranslations.has[attribute] || attribute;
+    var node = $(element).getAttributeNode(attribute);
+    return node && node.specified;
+  }
+};
+
+Element.Methods.ByTag = { };
+
+Object.extend(Element, Element.Methods);
+
+if (!Prototype.BrowserFeatures.ElementExtensions &&
+    document.createElement('div').__proto__) {
+  window.HTMLElement = { };
+  window.HTMLElement.prototype = document.createElement('div').__proto__;
+  Prototype.BrowserFeatures.ElementExtensions = true;
+}
+
+Element.extend = (function() {
+  if (Prototype.BrowserFeatures.SpecificElementExtensions)
+    return Prototype.K;
+
+  var Methods = { }, ByTag = Element.Methods.ByTag;
+
+  var extend = Object.extend(function(element) {
+    if (!element || element._extendedByPrototype ||
+        element.nodeType != 1 || element == window) return element;
+
+    var methods = Object.clone(Methods),
+      tagName = element.tagName, property, value;
+
+    // extend methods for specific tags
+    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
+
+    for (property in methods) {
+      value = methods[property];
+      if (Object.isFunction(value) && !(property in element))
+        element[property] = value.methodize();
+    }
+
+    element._extendedByPrototype = Prototype.emptyFunction;
+    return element;
+
+  }, {
+    refresh: function() {
+      // extend methods for all tags (Safari doesn't need this)
+      if (!Prototype.BrowserFeatures.ElementExtensions) {
+        Object.extend(Methods, Element.Methods);
+        Object.extend(Methods, Element.Methods.Simulated);
+      }
+    }
+  });
+
+  extend.refresh();
+  return extend;
+})();
+
+Element.hasAttribute = function(element, attribute) {
+  if (element.hasAttribute) return element.hasAttribute(attribute);
+  return Element.Methods.Simulated.hasAttribute(element, attribute);
+};
+
+Element.addMethods = function(methods) {
+  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
+
+  if (!methods) {
+    Object.extend(Form, Form.Methods);
+    Object.extend(Form.Element, Form.Element.Methods);
+    Object.extend(Element.Methods.ByTag, {
+      "FORM":     Object.clone(Form.Methods),
+      "INPUT":    Object.clone(Form.Element.Methods),
+      "SELECT":   Object.clone(Form.Element.Methods),
+      "TEXTAREA": Object.clone(Form.Element.Methods)
+    });
+  }
+
+  if (arguments.length == 2) {
+    var tagName = methods;
+    methods = arguments[1];
+  }
+
+  if (!tagName) Object.extend(Element.Methods, methods || { });
+  else {
+    if (Object.isArray(tagName)) tagName.each(extend);
+    else extend(tagName);
+  }
+
+  function extend(tagName) {
+    tagName = tagName.toUpperCase();
+    if (!Element.Methods.ByTag[tagName])
+      Element.Methods.ByTag[tagName] = { };
+    Object.extend(Element.Methods.ByTag[tagName], methods);
+  }
+
+  function copy(methods, destination, onlyIfAbsent) {
+    onlyIfAbsent = onlyIfAbsent || false;
+    for (var property in methods) {
+      var value = methods[property];
+      if (!Object.isFunction(value)) continue;
+      if (!onlyIfAbsent || !(property in destination))
+        destination[property] = value.methodize();
+    }
+  }
+
+  function findDOMClass(tagName) {
+    var klass;
+    var trans = {
+      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
+      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
+      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
+      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
+      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
+      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
+      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
+      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
+      "FrameSet", "IFRAME": "IFrame"
+    };
+    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName + 'Element';
+    if (window[klass]) return window[klass];
+    klass = 'HTML' + tagName.capitalize() + 'Element';
+    if (window[klass]) return window[klass];
+
+    window[klass] = { };
+    window[klass].prototype = document.createElement(tagName).__proto__;
+    return window[klass];
+  }
+
+  if (F.ElementExtensions) {
+    copy(Element.Methods, HTMLElement.prototype);
+    copy(Element.Methods.Simulated, HTMLElement.prototype, true);
+  }
+
+  if (F.SpecificElementExtensions) {
+    for (var tag in Element.Methods.ByTag) {
+      var klass = findDOMClass(tag);
+      if (Object.isUndefined(klass)) continue;
+      copy(T[tag], klass.prototype);
+    }
+  }
+
+  Object.extend(Element, Element.Methods);
+  delete Element.ByTag;
+
+  if (Element.extend.refresh) Element.extend.refresh();
+  Element.cache = { };
+};
+
+document.viewport = {
+  getDimensions: function() {
+    var dimensions = { };
+    var B = Prototype.Browser;
+    $w('width height').each(function(d) {
+      var D = d.capitalize();
+      dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] :
+        (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D];
+    });
+    return dimensions;
+  },
+
+  getWidth: function() {
+    return this.getDimensions().width;
+  },
+
+  getHeight: function() {
+    return this.getDimensions().height;
+  },
+
+  getScrollOffsets: function() {
+    return Element._returnOffset(
+      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
+      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
+  }
+};
+/* Portions of the Selector class are derived from Jack Slocum’s DomQuery,
+ * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
+ * license.  Please see http://www.yui-ext.com/ for more information. */
+
+var Selector = Class.create({
+  initialize: function(expression) {
+    this.expression = expression.strip();
+    this.compileMatcher();
+  },
+
+  shouldUseXPath: function() {
+    if (!Prototype.BrowserFeatures.XPath) return false;
+
+    var e = this.expression;
+
+    // Safari 3 chokes on :*-of-type and :empty
+    if (Prototype.Browser.WebKit &&
+     (e.include("-of-type") || e.include(":empty")))
+      return false;
+
+    // XPath can't do namespaced attributes, nor can it read
+    // the "checked" property from DOM nodes
+    if ((/(\[[\w-]*?:|:checked)/).test(this.expression))
+      return false;
+
+    return true;
+  },
+
+  compileMatcher: function() {
+    if (this.shouldUseXPath())
+      return this.compileXPathMatcher();
+
+    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
+        c = Selector.criteria, le, p, m;
+
+    if (Selector._cache[e]) {
+      this.matcher = Selector._cache[e];
+      return;
+    }
+
+    this.matcher = ["this.matcher = function(root) {",
+                    "var r = root, h = Selector.handlers, c = false, n;"];
+
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        p = ps[i];
+        if (m = e.match(p)) {
+          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
+    	      new Template(c[i]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.matcher.push("return h.unique(n);\n}");
+    eval(this.matcher.join('\n'));
+    Selector._cache[this.expression] = this.matcher;
+  },
+
+  compileXPathMatcher: function() {
+    var e = this.expression, ps = Selector.patterns,
+        x = Selector.xpath, le, m;
+
+    if (Selector._cache[e]) {
+      this.xpath = Selector._cache[e]; return;
+    }
+
+    this.matcher = ['.//*'];
+    while (e && le != e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        if (m = e.match(ps[i])) {
+          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
+            new Template(x[i]).evaluate(m));
+          e = e.replace(m[0], '');
+          break;
+        }
+      }
+    }
+
+    this.xpath = this.matcher.join('');
+    Selector._cache[this.expression] = this.xpath;
+  },
+
+  findElements: function(root) {
+    root = root || document;
+    if (this.xpath) return document._getElementsByXPath(this.xpath, root);
+    return this.matcher(root);
+  },
+
+  match: function(element) {
+    this.tokens = [];
+
+    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
+    var le, p, m;
+
+    while (e && le !== e && (/\S/).test(e)) {
+      le = e;
+      for (var i in ps) {
+        p = ps[i];
+        if (m = e.match(p)) {
+          // use the Selector.assertions methods unless the selector
+          // is too complex.
+          if (as[i]) {
+            this.tokens.push([i, Object.clone(m)]);
+            e = e.replace(m[0], '');
+          } else {
+            // reluctantly do a document-wide search
+            // and look for a match in the array
+            return this.findElements(document).include(element);
+          }
+        }
+      }
+    }
+
+    var match = true, name, matches;
+    for (var i = 0, token; token = this.tokens[i]; i++) {
+      name = token[0], matches = token[1];
+      if (!Selector.assertions[name](element, matches)) {
+        match = false; break;
+      }
+    }
+
+    return match;
+  },
+
+  toString: function() {
+    return this.expression;
+  },
+
+  inspect: function() {
+    return "#<Selector:" + this.expression.inspect() + ">";
+  }
+});
+
+Object.extend(Selector, {
+  _cache: { },
+
+  xpath: {
+    descendant:   "//*",
+    child:        "/*",
+    adjacent:     "/following-sibling::*[1]",
+    laterSibling: '/following-sibling::*',
+    tagName:      function(m) {
+      if (m[1] == '*') return '';
+      return "[local-name()='" + m[1].toLowerCase() +
+             "' or local-name()='" + m[1].toUpperCase() + "']";
+    },
+    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
+    id:           "[@id='#{1}']",
+    attrPresence: function(m) {
+      m[1] = m[1].toLowerCase();
+      return new Template("[@#{1}]").evaluate(m);
+    },
+    attr: function(m) {
+      m[1] = m[1].toLowerCase();
+      m[3] = m[5] || m[6];
+      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
+    },
+    pseudo: function(m) {
+      var h = Selector.xpath.pseudos[m[1]];
+      if (!h) return '';
+      if (Object.isFunction(h)) return h(m);
+      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
+    },
+    operators: {
+      '=':  "[@#{1}='#{3}']",
+      '!=': "[@#{1}!='#{3}']",
+      '^=': "[starts-with(@#{1}, '#{3}')]",
+      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
+      '*=': "[contains(@#{1}, '#{3}')]",
+      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
+      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
+    },
+    pseudos: {
+      'first-child': '[not(preceding-sibling::*)]',
+      'last-child':  '[not(following-sibling::*)]',
+      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
+      'empty':       "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]",
+      'checked':     "[@checked]",
+      'disabled':    "[@disabled]",
+      'enabled':     "[not(@disabled)]",
+      'not': function(m) {
+        var e = m[6], p = Selector.patterns,
+            x = Selector.xpath, le, v;
+
+        var exclusion = [];
+        while (e && le != e && (/\S/).test(e)) {
+          le = e;
+          for (var i in p) {
+            if (m = e.match(p[i])) {
+              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
+              exclusion.push("(" + v.substring(1, v.length - 1) + ")");
+              e = e.replace(m[0], '');
+              break;
+            }
+          }
+        }
+        return "[not(" + exclusion.join(" and ") + ")]";
+      },
+      'nth-child':      function(m) {
+        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
+      },
+      'nth-last-child': function(m) {
+        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
+      },
+      'nth-of-type':    function(m) {
+        return Selector.xpath.pseudos.nth("position() ", m);
+      },
+      'nth-last-of-type': function(m) {
+        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
+      },
+      'first-of-type':  function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
+      },
+      'last-of-type':   function(m) {
+        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
+      },
+      'only-of-type':   function(m) {
+        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
+      },
+      nth: function(fragment, m) {
+        var mm, formula = m[6], predicate;
+        if (formula == 'even') formula = '2n+0';
+        if (formula == 'odd')  formula = '2n+1';
+        if (mm = formula.match(/^(\d+)$/)) // digit only
+          return '[' + fragment + "= " + mm[1] + ']';
+        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+          if (mm[1] == "-") mm[1] = -1;
+          var a = mm[1] ? Number(mm[1]) : 1;
+          var b = mm[2] ? Number(mm[2]) : 0;
+          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
+          "((#{fragment} - #{b}) div #{a} >= 0)]";
+          return new Template(predicate).evaluate({
+            fragment: fragment, a: a, b: b });
+        }
+      }
+    }
+  },
+
+  criteria: {
+    tagName:      'n = h.tagName(n, r, "#{1}", c);      c = false;',
+    className:    'n = h.className(n, r, "#{1}", c);    c = false;',
+    id:           'n = h.id(n, r, "#{1}", c);           c = false;',
+    attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
+    attr: function(m) {
+      m[3] = (m[5] || m[6]);
+      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
+    },
+    pseudo: function(m) {
+      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
+      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
+    },
+    descendant:   'c = "descendant";',
+    child:        'c = "child";',
+    adjacent:     'c = "adjacent";',
+    laterSibling: 'c = "laterSibling";'
+  },
+
+  patterns: {
+    // combinators must be listed first
+    // (and descendant needs to be last combinator)
+    laterSibling: /^\s*~\s*/,
+    child:        /^\s*>\s*/,
+    adjacent:     /^\s*\+\s*/,
+    descendant:   /^\s/,
+
+    // selectors follow
+    tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
+    id:           /^#([\w\-\*]+)(\b|$)/,
+    className:    /^\.([\w\-\*]+)(\b|$)/,
+    pseudo:
+/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
+    attrPresence: /^\[([\w]+)\]/,
+    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
+  },
+
+  // for Selector.match and Element#match
+  assertions: {
+    tagName: function(element, matches) {
+      return matches[1].toUpperCase() == element.tagName.toUpperCase();
+    },
+
+    className: function(element, matches) {
+      return Element.hasClassName(element, matches[1]);
+    },
+
+    id: function(element, matches) {
+      return element.id === matches[1];
+    },
+
+    attrPresence: function(element, matches) {
+      return Element.hasAttribute(element, matches[1]);
+    },
+
+    attr: function(element, matches) {
+      var nodeValue = Element.readAttribute(element, matches[1]);
+      return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
+    }
+  },
+
+  handlers: {
+    // UTILITY FUNCTIONS
+    // joins two collections
+    concat: function(a, b) {
+      for (var i = 0, node; node = b[i]; i++)
+        a.push(node);
+      return a;
+    },
+
+    // marks an array of nodes for counting
+    mark: function(nodes) {
+      var _true = Prototype.emptyFunction;
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._countedByPrototype = _true;
+      return nodes;
+    },
+
+    unmark: function(nodes) {
+      for (var i = 0, node; node = nodes[i]; i++)
+        node._countedByPrototype = undefined;
+      return nodes;
+    },
+
+    // mark each child node with its position (for nth calls)
+    // "ofType" flag indicates whether we're indexing for nth-of-type
+    // rather than nth-child
+    index: function(parentNode, reverse, ofType) {
+      parentNode._countedByPrototype = Prototype.emptyFunction;
+      if (reverse) {
+        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
+          var node = nodes[i];
+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
+        }
+      } else {
+        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
+          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
+      }
+    },
+
+    // filters out duplicates and extends all nodes
+    unique: function(nodes) {
+      if (nodes.length == 0) return nodes;
+      var results = [], n;
+      for (var i = 0, l = nodes.length; i < l; i++)
+        if (!(n = nodes[i])._countedByPrototype) {
+          n._countedByPrototype = Prototype.emptyFunction;
+          results.push(Element.extend(n));
+        }
+      return Selector.handlers.unmark(results);
+    },
+
+    // COMBINATOR FUNCTIONS
+    descendant: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, node.getElementsByTagName('*'));
+      return results;
+    },
+
+    child: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        for (var j = 0, child; child = node.childNodes[j]; j++)
+          if (child.nodeType == 1 && child.tagName != '!') results.push(child);
+      }
+      return results;
+    },
+
+    adjacent: function(nodes) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        var next = this.nextElementSibling(node);
+        if (next) results.push(next);
+      }
+      return results;
+    },
+
+    laterSibling: function(nodes) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        h.concat(results, Element.nextSiblings(node));
+      return results;
+    },
+
+    nextElementSibling: function(node) {
+      while (node = node.nextSibling)
+	      if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    previousElementSibling: function(node) {
+      while (node = node.previousSibling)
+        if (node.nodeType == 1) return node;
+      return null;
+    },
+
+    // TOKEN FUNCTIONS
+    tagName: function(nodes, root, tagName, combinator) {
+      var uTagName = tagName.toUpperCase();
+      var results = [], h = Selector.handlers;
+      if (nodes) {
+        if (combinator) {
+          // fastlane for ordinary descendant combinators
+          if (combinator == "descendant") {
+            for (var i = 0, node; node = nodes[i]; i++)
+              h.concat(results, node.getElementsByTagName(tagName));
+            return results;
+          } else nodes = this[combinator](nodes);
+          if (tagName == "*") return nodes;
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.tagName.toUpperCase() === uTagName) results.push(node);
+        return results;
+      } else return root.getElementsByTagName(tagName);
+    },
+
+    id: function(nodes, root, id, combinator) {
+      var targetNode = $(id), h = Selector.handlers;
+      if (!targetNode) return [];
+      if (!nodes && root == document) return [targetNode];
+      if (nodes) {
+        if (combinator) {
+          if (combinator == 'child') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (targetNode.parentNode == node) return [targetNode];
+          } else if (combinator == 'descendant') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Element.descendantOf(targetNode, node)) return [targetNode];
+          } else if (combinator == 'adjacent') {
+            for (var i = 0, node; node = nodes[i]; i++)
+              if (Selector.handlers.previousElementSibling(targetNode) == node)
+                return [targetNode];
+          } else nodes = h[combinator](nodes);
+        }
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node == targetNode) return [targetNode];
+        return [];
+      }
+      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
+    },
+
+    className: function(nodes, root, className, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      return Selector.handlers.byClassName(nodes, root, className);
+    },
+
+    byClassName: function(nodes, root, className) {
+      if (!nodes) nodes = Selector.handlers.descendant([root]);
+      var needle = ' ' + className + ' ';
+      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
+        nodeClassName = node.className;
+        if (nodeClassName.length == 0) continue;
+        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
+          results.push(node);
+      }
+      return results;
+    },
+
+    attrPresence: function(nodes, root, attr, combinator) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      var results = [];
+      for (var i = 0, node; node = nodes[i]; i++)
+        if (Element.hasAttribute(node, attr)) results.push(node);
+      return results;
+    },
+
+    attr: function(nodes, root, attr, value, operator, combinator) {
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      var handler = Selector.operators[operator], results = [];
+      for (var i = 0, node; node = nodes[i]; i++) {
+        var nodeValue = Element.readAttribute(node, attr);
+        if (nodeValue === null) continue;
+        if (handler(nodeValue, value)) results.push(node);
+      }
+      return results;
+    },
+
+    pseudo: function(nodes, name, value, root, combinator) {
+      if (nodes && combinator) nodes = this[combinator](nodes);
+      if (!nodes) nodes = root.getElementsByTagName("*");
+      return Selector.pseudos[name](nodes, value, root);
+    }
+  },
+
+  pseudos: {
+    'first-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.previousElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'last-child': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        if (Selector.handlers.nextElementSibling(node)) continue;
+          results.push(node);
+      }
+      return results;
+    },
+    'only-child': function(nodes, value, root) {
+      var h = Selector.handlers;
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
+          results.push(node);
+      return results;
+    },
+    'nth-child':        function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root);
+    },
+    'nth-last-child':   function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true);
+    },
+    'nth-of-type':      function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, false, true);
+    },
+    'nth-last-of-type': function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, formula, root, true, true);
+    },
+    'first-of-type':    function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, false, true);
+    },
+    'last-of-type':     function(nodes, formula, root) {
+      return Selector.pseudos.nth(nodes, "1", root, true, true);
+    },
+    'only-of-type':     function(nodes, formula, root) {
+      var p = Selector.pseudos;
+      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
+    },
+
+    // handles the an+b logic
+    getIndices: function(a, b, total) {
+      if (a == 0) return b > 0 ? [b] : [];
+      return $R(1, total).inject([], function(memo, i) {
+        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
+        return memo;
+      });
+    },
+
+    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
+    nth: function(nodes, formula, root, reverse, ofType) {
+      if (nodes.length == 0) return [];
+      if (formula == 'even') formula = '2n+0';
+      if (formula == 'odd')  formula = '2n+1';
+      var h = Selector.handlers, results = [], indexed = [], m;
+      h.mark(nodes);
+      for (var i = 0, node; node = nodes[i]; i++) {
+        if (!node.parentNode._countedByPrototype) {
+          h.index(node.parentNode, reverse, ofType);
+          indexed.push(node.parentNode);
+        }
+      }
+      if (formula.match(/^\d+$/)) { // just a number
+        formula = Number(formula);
+        for (var i = 0, node; node = nodes[i]; i++)
+          if (node.nodeIndex == formula) results.push(node);
+      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
+        if (m[1] == "-") m[1] = -1;
+        var a = m[1] ? Number(m[1]) : 1;
+        var b = m[2] ? Number(m[2]) : 0;
+        var indices = Selector.pseudos.getIndices(a, b, nodes.length);
+        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
+          for (var j = 0; j < l; j++)
+            if (node.nodeIndex == indices[j]) results.push(node);
+        }
+      }
+      h.unmark(nodes);
+      h.unmark(indexed);
+      return results;
+    },
+
+    'empty': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++) {
+        // IE treats comments as element nodes
+        if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue;
+        results.push(node);
+      }
+      return results;
+    },
+
+    'not': function(nodes, selector, root) {
+      var h = Selector.handlers, selectorType, m;
+      var exclusions = new Selector(selector).findElements(root);
+      h.mark(exclusions);
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node._countedByPrototype) results.push(node);
+      h.unmark(exclusions);
+      return results;
+    },
+
+    'enabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (!node.disabled) results.push(node);
+      return results;
+    },
+
+    'disabled': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.disabled) results.push(node);
+      return results;
+    },
+
+    'checked': function(nodes, value, root) {
+      for (var i = 0, results = [], node; node = nodes[i]; i++)
+        if (node.checked) results.push(node);
+      return results;
+    }
+  },
+
+  operators: {
+    '=':  function(nv, v) { return nv == v; },
+    '!=': function(nv, v) { return nv != v; },
+    '^=': function(nv, v) { return nv.startsWith(v); },
+    '$=': function(nv, v) { return nv.endsWith(v); },
+    '*=': function(nv, v) { return nv.include(v); },
+    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
+    '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); }
+  },
+
+  split: function(expression) {
+    var expressions = [];
+    expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
+      expressions.push(m[1].strip());
+    });
+    return expressions;
+  },
+
+  matchElements: function(elements, expression) {
+    var matches = $$(expression), h = Selector.handlers;
+    h.mark(matches);
+    for (var i = 0, results = [], element; element = elements[i]; i++)
+      if (element._countedByPrototype) results.push(element);
+    h.unmark(matches);
+    return results;
+  },
+
+  findElement: function(elements, expression, index) {
+    if (Object.isNumber(expression)) {
+      index = expression; expression = false;
+    }
+    return Selector.matchElements(elements, expression || '*')[index || 0];
+  },
+
+  findChildElements: function(element, expressions) {
+    expressions = Selector.split(expressions.join(','));
+    var results = [], h = Selector.handlers;
+    for (var i = 0, l = expressions.length, selector; i < l; i++) {
+      selector = new Selector(expressions[i].strip());
+      h.concat(results, selector.findElements(element));
+    }
+    return (l > 1) ? h.unique(results) : results;
+  }
+});
+
+if (Prototype.Browser.IE) {
+  Object.extend(Selector.handlers, {
+    // IE returns comment nodes on getElementsByTagName("*").
+    // Filter them out.
+    concat: function(a, b) {
+      for (var i = 0, node; node = b[i]; i++)
+        if (node.tagName !== "!") a.push(node);
+      return a;
+    },
+
+    // IE improperly serializes _countedByPrototype in (inner|outer)HTML.
+    unmark: function(nodes) {
+      for (var i = 0, node; node = nodes[i]; i++)
+        node.removeAttribute('_countedByPrototype');
+      return nodes;
+    }
+  });
+}
+
+function $$() {
+  return Selector.findChildElements(document, $A(arguments));
+}
+var Form = {
+  reset: function(form) {
+    $(form).reset();
+    return form;
+  },
+
+  serializeElements: function(elements, options) {
+    if (typeof options != 'object') options = { hash: !!options };
+    else if (Object.isUndefined(options.hash)) options.hash = true;
+    var key, value, submitted = false, submit = options.submit;
+
+    var data = elements.inject({ }, function(result, element) {
+      if (!element.disabled && element.name) {
+        key = element.name; value = $(element).getValue();
+        if (value != null && (element.type != 'submit' || (!submitted &&
+            submit !== false && (!submit || key == submit) && (submitted = true)))) {
+          if (key in result) {
+            // a key is already present; construct an array of values
+            if (!Object.isArray(result[key])) result[key] = [result[key]];
+            result[key].push(value);
+          }
+          else result[key] = value;
+        }
+      }
+      return result;
+    });
+
+    return options.hash ? data : Object.toQueryString(data);
+  }
+};
+
+Form.Methods = {
+  serialize: function(form, options) {
+    return Form.serializeElements(Form.getElements(form), options);
+  },
+
+  getElements: function(form) {
+    return $A($(form).getElementsByTagName('*')).inject([],
+      function(elements, child) {
+        if (Form.Element.Serializers[child.tagName.toLowerCase()])
+          elements.push(Element.extend(child));
+        return elements;
+      }
+    );
+  },
+
+  getInputs: function(form, typeName, name) {
+    form = $(form);
+    var inputs = form.getElementsByTagName('input');
+
+    if (!typeName && !name) return $A(inputs).map(Element.extend);
+
+    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
+      var input = inputs[i];
+      if ((typeName && input.type != typeName) || (name && input.name != name))
+        continue;
+      matchingInputs.push(Element.extend(input));
+    }
+
+    return matchingInputs;
+  },
+
+  disable: function(form) {
+    form = $(form);
+    Form.getElements(form).invoke('disable');
+    return form;
+  },
+
+  enable: function(form) {
+    form = $(form);
+    Form.getElements(form).invoke('enable');
+    return form;
+  },
+
+  findFirstElement: function(form) {
+    var elements = $(form).getElements().findAll(function(element) {
+      return 'hidden' != element.type && !element.disabled;
+    });
+    var firstByIndex = elements.findAll(function(element) {
+      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
+    }).sortBy(function(element) { return element.tabIndex }).first();
+
+    return firstByIndex ? firstByIndex : elements.find(function(element) {
+      return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
+    });
+  },
+
+  focusFirstElement: function(form) {
+    form = $(form);
+    form.findFirstElement().activate();
+    return form;
+  },
+
+  request: function(form, options) {
+    form = $(form), options = Object.clone(options || { });
+
+    var params = options.parameters, action = form.readAttribute('action') || '';
+    if (action.blank()) action = window.location.href;
+    options.parameters = form.serialize(true);
+
+    if (params) {
+      if (Object.isString(params)) params = params.toQueryParams();
+      Object.extend(options.parameters, params);
+    }
+
+    if (form.hasAttribute('method') && !options.method)
+      options.method = form.method;
+
+    return new Ajax.Request(action, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element = {
+  focus: function(element) {
+    $(element).focus();
+    return element;
+  },
+
+  select: function(element) {
+    $(element).select();
+    return element;
+  }
+};
+
+Form.Element.Methods = {
+  serialize: function(element) {
+    element = $(element);
+    if (!element.disabled && element.name) {
+      var value = element.getValue();
+      if (value != undefined) {
+        var pair = { };
+        pair[element.name] = value;
+        return Object.toQueryString(pair);
+      }
+    }
+    return '';
+  },
+
+  getValue: function(element) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    return Form.Element.Serializers[method](element);
+  },
+
+  setValue: function(element, value) {
+    element = $(element);
+    var method = element.tagName.toLowerCase();
+    Form.Element.Serializers[method](element, value);
+    return element;
+  },
+
+  clear: function(element) {
+    $(element).value = '';
+    return element;
+  },
+
+  present: function(element) {
+    return $(element).value != '';
+  },
+
+  activate: function(element) {
+    element = $(element);
+    try {
+      element.focus();
+      if (element.select && (element.tagName.toLowerCase() != 'input' ||
+          !['button', 'reset', 'submit'].include(element.type)))
+        element.select();
+    } catch (e) { }
+    return element;
+  },
+
+  disable: function(element) {
+    element = $(element);
+    element.blur();
+    element.disabled = true;
+    return element;
+  },
+
+  enable: function(element) {
+    element = $(element);
+    element.disabled = false;
+    return element;
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+var Field = Form.Element;
+var $F = Form.Element.Methods.getValue;
+
+/*--------------------------------------------------------------------------*/
+
+Form.Element.Serializers = {
+  input: function(element, value) {
+    switch (element.type.toLowerCase()) {
+      case 'checkbox':
+      case 'radio':
+        return Form.Element.Serializers.inputSelector(element, value);
+      default:
+        return Form.Element.Serializers.textarea(element, value);
+    }
+  },
+
+  inputSelector: function(element, value) {
+    if (Object.isUndefined(value)) return element.checked ? element.value : null;
+    else element.checked = !!value;
+  },
+
+  textarea: function(element, value) {
+    if (Object.isUndefined(value)) return element.value;
+    else element.value = value;
+  },
+
+  select: function(element, index) {
+    if (Object.isUndefined(index))
+      return this[element.type == 'select-one' ?
+        'selectOne' : 'selectMany'](element);
+    else {
+      var opt, value, single = !Object.isArray(index);
+      for (var i = 0, length = element.length; i < length; i++) {
+        opt = element.options[i];
+        value = this.optionValue(opt);
+        if (single) {
+          if (value == index) {
+            opt.selected = true;
+            return;
+          }
+        }
+        else opt.selected = index.include(value);
+      }
+    }
+  },
+
+  selectOne: function(element) {
+    var index = element.selectedIndex;
+    return index >= 0 ? this.optionValue(element.options[index]) : null;
+  },
+
+  selectMany: function(element) {
+    var values, length = element.length;
+    if (!length) return null;
+
+    for (var i = 0, values = []; i < length; i++) {
+      var opt = element.options[i];
+      if (opt.selected) values.push(this.optionValue(opt));
+    }
+    return values;
+  },
+
+  optionValue: function(opt) {
+    // extend element because hasAttribute may not be native
+    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
+  initialize: function($super, element, frequency, callback) {
+    $super(callback, frequency);
+    this.element   = $(element);
+    this.lastValue = this.getValue();
+  },
+
+  execute: function() {
+    var value = this.getValue();
+    if (Object.isString(this.lastValue) && Object.isString(value) ?
+        this.lastValue != value : String(this.lastValue) != String(value)) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  }
+});
+
+Form.Element.Observer = Class.create(Abstract.TimedObserver, {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.Observer = Class.create(Abstract.TimedObserver, {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+
+/*--------------------------------------------------------------------------*/
+
+Abstract.EventObserver = Class.create({
+  initialize: function(element, callback) {
+    this.element  = $(element);
+    this.callback = callback;
+
+    this.lastValue = this.getValue();
+    if (this.element.tagName.toLowerCase() == 'form')
+      this.registerFormCallbacks();
+    else
+      this.registerCallback(this.element);
+  },
+
+  onElementEvent: function() {
+    var value = this.getValue();
+    if (this.lastValue != value) {
+      this.callback(this.element, value);
+      this.lastValue = value;
+    }
+  },
+
+  registerFormCallbacks: function() {
+    Form.getElements(this.element).each(this.registerCallback, this);
+  },
+
+  registerCallback: function(element) {
+    if (element.type) {
+      switch (element.type.toLowerCase()) {
+        case 'checkbox':
+        case 'radio':
+          Event.observe(element, 'click', this.onElementEvent.bind(this));
+          break;
+        default:
+          Event.observe(element, 'change', this.onElementEvent.bind(this));
+          break;
+      }
+    }
+  }
+});
+
+Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
+  getValue: function() {
+    return Form.Element.getValue(this.element);
+  }
+});
+
+Form.EventObserver = Class.create(Abstract.EventObserver, {
+  getValue: function() {
+    return Form.serialize(this.element);
+  }
+});
+if (!window.Event) var Event = { };
+
+Object.extend(Event, {
+  KEY_BACKSPACE: 8,
+  KEY_TAB:       9,
+  KEY_RETURN:   13,
+  KEY_ESC:      27,
+  KEY_LEFT:     37,
+  KEY_UP:       38,
+  KEY_RIGHT:    39,
+  KEY_DOWN:     40,
+  KEY_DELETE:   46,
+  KEY_HOME:     36,
+  KEY_END:      35,
+  KEY_PAGEUP:   33,
+  KEY_PAGEDOWN: 34,
+  KEY_INSERT:   45,
+
+  cache: { },
+
+  relatedTarget: function(event) {
+    var element;
+    switch(event.type) {
+      case 'mouseover': element = event.fromElement; break;
+      case 'mouseout':  element = event.toElement;   break;
+      default: return null;
+    }
+    return Element.extend(element);
+  }
+});
+
+Event.Methods = (function() {
+  var isButton;
+
+  if (Prototype.Browser.IE) {
+    var buttonMap = { 0: 1, 1: 4, 2: 2 };
+    isButton = function(event, code) {
+      return event.button == buttonMap[code];
+    };
+
+  } else if (Prototype.Browser.WebKit) {
+    isButton = function(event, code) {
+      switch (code) {
+        case 0: return event.which == 1 && !event.metaKey;
+        case 1: return event.which == 1 && event.metaKey;
+        default: return false;
+      }
+    };
+
+  } else {
+    isButton = function(event, code) {
+      return event.which ? (event.which === code + 1) : (event.button === code);
+    };
+  }
+
+  return {
+    isLeftClick:   function(event) { return isButton(event, 0) },
+    isMiddleClick: function(event) { return isButton(event, 1) },
+    isRightClick:  function(event) { return isButton(event, 2) },
+
+    element: function(event) {
+      var node = Event.extend(event).target;
+      return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node);
+    },
+
+    findElement: function(event, expression) {
+      var element = Event.element(event);
+      if (!expression) return element;
+      var elements = [element].concat(element.ancestors());
+      return Selector.findElement(elements, expression, 0);
+    },
+
+    pointer: function(event) {
+      return {
+        x: event.pageX || (event.clientX +
+          (document.documentElement.scrollLeft || document.body.scrollLeft)),
+        y: event.pageY || (event.clientY +
+          (document.documentElement.scrollTop || document.body.scrollTop))
+      };
+    },
+
+    pointerX: function(event) { return Event.pointer(event).x },
+    pointerY: function(event) { return Event.pointer(event).y },
+
+    stop: function(event) {
+      Event.extend(event);
+      event.preventDefault();
+      event.stopPropagation();
+      event.stopped = true;
+    }
+  };
+})();
+
+Event.extend = (function() {
+  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
+    m[name] = Event.Methods[name].methodize();
+    return m;
+  });
+
+  if (Prototype.Browser.IE) {
+    Object.extend(methods, {
+      stopPropagation: function() { this.cancelBubble = true },
+      preventDefault:  function() { this.returnValue = false },
+      inspect: function() { return "[object Event]" }
+    });
+
+    return function(event) {
+      if (!event) return false;
+      if (event._extendedByPrototype) return event;
+
+      event._extendedByPrototype = Prototype.emptyFunction;
+      var pointer = Event.pointer(event);
+      Object.extend(event, {
+        target: event.srcElement,
+        relatedTarget: Event.relatedTarget(event),
+        pageX:  pointer.x,
+        pageY:  pointer.y
+      });
+      return Object.extend(event, methods);
+    };
+
+  } else {
+    Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__;
+    Object.extend(Event.prototype, methods);
+    return Prototype.K;
+  }
+})();
+
+Object.extend(Event, (function() {
+  var cache = Event.cache;
+
+  function getEventID(element) {
+    if (element._prototypeEventID) return element._prototypeEventID[0];
+    arguments.callee.id = arguments.callee.id || 1;
+    return element._prototypeEventID = [++arguments.callee.id];
+  }
+
+  function getDOMEventName(eventName) {
+    if (eventName && eventName.include(':')) return "dataavailable";
+    return eventName;
+  }
+
+  function getCacheForID(id) {
+    return cache[id] = cache[id] || { };
+  }
+
+  function getWrappersForEventName(id, eventName) {
+    var c = getCacheForID(id);
+    return c[eventName] = c[eventName] || [];
+  }
+
+  function createWrapper(element, eventName, handler) {
+    var id = getEventID(element);
+    var c = getWrappersForEventName(id, eventName);
+    if (c.pluck("handler").include(handler)) return false;
+
+    var wrapper = function(event) {
+      if (!Event || !Event.extend ||
+        (event.eventName && event.eventName != eventName))
+          return false;
+
+      Event.extend(event);
+      handler.call(element, event);
+    };
+
+    wrapper.handler = handler;
+    c.push(wrapper);
+    return wrapper;
+  }
+
+  function findWrapper(id, eventName, handler) {
+    var c = getWrappersForEventName(id, eventName);
+    return c.find(function(wrapper) { return wrapper.handler == handler });
+  }
+
+  function destroyWrapper(id, eventName, handler) {
+    var c = getCacheForID(id);
+    if (!c[eventName]) return false;
+    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
+  }
+
+  function destroyCache() {
+    for (var id in cache)
+      for (var eventName in cache[id])
+        cache[id][eventName] = null;
+  }
+
+  if (window.attachEvent) {
+    window.attachEvent("onunload", destroyCache);
+  }
+
+  return {
+    observe: function(element, eventName, handler) {
+      element = $(element);
+      var name = getDOMEventName(eventName);
+
+      var wrapper = createWrapper(element, eventName, handler);
+      if (!wrapper) return element;
+
+      if (element.addEventListener) {
+        element.addEventListener(name, wrapper, false);
+      } else {
+        element.attachEvent("on" + name, wrapper);
+      }
+
+      return element;
+    },
+
+    stopObserving: function(element, eventName, handler) {
+      element = $(element);
+      var id = getEventID(element), name = getDOMEventName(eventName);
+
+      if (!handler && eventName) {
+        getWrappersForEventName(id, eventName).each(function(wrapper) {
+          element.stopObserving(eventName, wrapper.handler);
+        });
+        return element;
+
+      } else if (!eventName) {
+        Object.keys(getCacheForID(id)).each(function(eventName) {
+          element.stopObserving(eventName);
+        });
+        return element;
+      }
+
+      var wrapper = findWrapper(id, eventName, handler);
+      if (!wrapper) return element;
+
+      if (element.removeEventListener) {
+        element.removeEventListener(name, wrapper, false);
+      } else {
+        element.detachEvent("on" + name, wrapper);
+      }
+
+      destroyWrapper(id, eventName, handler);
+
+      return element;
+    },
+
+    fire: function(element, eventName, memo) {
+      element = $(element);
+      if (element == document && document.createEvent && !element.dispatchEvent)
+        element = document.documentElement;
+
+      var event;
+      if (document.createEvent) {
+        event = document.createEvent("HTMLEvents");
+        event.initEvent("dataavailable", true, true);
+      } else {
+        event = document.createEventObject();
+        event.eventType = "ondataavailable";
+      }
+
+      event.eventName = eventName;
+      event.memo = memo || { };
+
+      if (document.createEvent) {
+        element.dispatchEvent(event);
+      } else {
+        element.fireEvent(event.eventType, event);
+      }
+
+      return Event.extend(event);
+    }
+  };
+})());
+
+Object.extend(Event, Event.Methods);
+
+Element.addMethods({
+  fire:          Event.fire,
+  observe:       Event.observe,
+  stopObserving: Event.stopObserving
+});
+
+Object.extend(document, {
+  fire:          Element.Methods.fire.methodize(),
+  observe:       Element.Methods.observe.methodize(),
+  stopObserving: Element.Methods.stopObserving.methodize(),
+  loaded:        false
+});
+
+(function() {
+  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
+     Matthias Miller, Dean Edwards and John Resig. */
+
+  var timer;
+
+  function fireContentLoadedEvent() {
+    if (document.loaded) return;
+    if (timer) window.clearInterval(timer);
+    document.fire("dom:loaded");
+    document.loaded = true;
+  }
+
+  if (document.addEventListener) {
+    if (Prototype.Browser.WebKit) {
+      timer = window.setInterval(function() {
+        if (/loaded|complete/.test(document.readyState))
+          fireContentLoadedEvent();
+      }, 0);
+
+      Event.observe(window, "load", fireContentLoadedEvent);
+
+    } else {
+      document.addEventListener("DOMContentLoaded",
+        fireContentLoadedEvent, false);
+    }
+
+  } else {
+    document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
+    $("__onDOMContentLoaded").onreadystatechange = function() {
+      if (this.readyState == "complete") {
+        this.onreadystatechange = null;
+        fireContentLoadedEvent();
+      }
+    };
+  }
+})();
+/*------------------------------- DEPRECATED -------------------------------*/
+
+Hash.toQueryString = Object.toQueryString;
+
+var Toggle = { display: Element.toggle };
+
+Element.Methods.childOf = Element.Methods.descendantOf;
+
+var Insertion = {
+  Before: function(element, content) {
+    return Element.insert(element, {before:content});
+  },
+
+  Top: function(element, content) {
+    return Element.insert(element, {top:content});
+  },
+
+  Bottom: function(element, content) {
+    return Element.insert(element, {bottom:content});
+  },
+
+  After: function(element, content) {
+    return Element.insert(element, {after:content});
+  }
+};
+
+var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
+
+// This should be moved to script.aculo.us; notice the deprecated methods
+// further below, that map to the newer Element methods.
+var Position = {
+  // set to true if needed, warning: firefox performance problems
+  // NOT neeeded for page scrolling, only if draggable contained in
+  // scrollable elements
+  includeScrollOffsets: false,
+
+  // must be called before calling withinIncludingScrolloffset, every time the
+  // page is scrolled
+  prepare: function() {
+    this.deltaX =  window.pageXOffset
+                || document.documentElement.scrollLeft
+                || document.body.scrollLeft
+                || 0;
+    this.deltaY =  window.pageYOffset
+                || document.documentElement.scrollTop
+                || document.body.scrollTop
+                || 0;
+  },
+
+  // caches x/y coordinate pair to use with overlap
+  within: function(element, x, y) {
+    if (this.includeScrollOffsets)
+      return this.withinIncludingScrolloffsets(element, x, y);
+    this.xcomp = x;
+    this.ycomp = y;
+    this.offset = Element.cumulativeOffset(element);
+
+    return (y >= this.offset[1] &&
+            y <  this.offset[1] + element.offsetHeight &&
+            x >= this.offset[0] &&
+            x <  this.offset[0] + element.offsetWidth);
+  },
+
+  withinIncludingScrolloffsets: function(element, x, y) {
+    var offsetcache = Element.cumulativeScrollOffset(element);
+
+    this.xcomp = x + offsetcache[0] - this.deltaX;
+    this.ycomp = y + offsetcache[1] - this.deltaY;
+    this.offset = Element.cumulativeOffset(element);
+
+    return (this.ycomp >= this.offset[1] &&
+            this.ycomp <  this.offset[1] + element.offsetHeight &&
+            this.xcomp >= this.offset[0] &&
+            this.xcomp <  this.offset[0] + element.offsetWidth);
+  },
+
+  // within must be called directly before
+  overlap: function(mode, element) {
+    if (!mode) return 0;
+    if (mode == 'vertical')
+      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
+        element.offsetHeight;
+    if (mode == 'horizontal')
+      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
+        element.offsetWidth;
+  },
+
+  // Deprecation layer -- use newer Element methods now (1.5.2).
+
+  cumulativeOffset: Element.Methods.cumulativeOffset,
+
+  positionedOffset: Element.Methods.positionedOffset,
+
+  absolutize: function(element) {
+    Position.prepare();
+    return Element.absolutize(element);
+  },
+
+  relativize: function(element) {
+    Position.prepare();
+    return Element.relativize(element);
+  },
+
+  realOffset: Element.Methods.cumulativeScrollOffset,
+
+  offsetParent: Element.Methods.getOffsetParent,
+
+  page: Element.Methods.viewportOffset,
+
+  clone: function(source, target, options) {
+    options = options || { };
+    return Element.clonePosition(target, source, options);
+  }
+};
+
+/*--------------------------------------------------------------------------*/
+
+if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
+  function iter(name) {
+    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
+  }
+
+  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
+  function(element, className) {
+    className = className.toString().strip();
+    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
+    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
+  } : function(element, className) {
+    className = className.toString().strip();
+    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
+    if (!classNames && !className) return elements;
+
+    var nodes = $(element).getElementsByTagName('*');
+    className = ' ' + className + ' ';
+
+    for (var i = 0, child, cn; child = nodes[i]; i++) {
+      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
+          (classNames && classNames.all(function(name) {
+            return !name.toString().blank() && cn.include(' ' + name + ' ');
+          }))))
+        elements.push(Element.extend(child));
+    }
+    return elements;
+  };
+
+  return function(className, parentElement) {
+    return $(parentElement || document.body).getElementsByClassName(className);
+  };
+}(Element.Methods);
+
+/*--------------------------------------------------------------------------*/
+
+Element.ClassNames = Class.create();
+Element.ClassNames.prototype = {
+  initialize: function(element) {
+    this.element = $(element);
+  },
+
+  _each: function(iterator) {
+    this.element.className.split(/\s+/).select(function(name) {
+      return name.length > 0;
+    })._each(iterator);
+  },
+
+  set: function(className) {
+    this.element.className = className;
+  },
+
+  add: function(classNameToAdd) {
+    if (this.include(classNameToAdd)) return;
+    this.set($A(this).concat(classNameToAdd).join(' '));
+  },
+
+  remove: function(classNameToRemove) {
+    if (!this.include(classNameToRemove)) return;
+    this.set($A(this).without(classNameToRemove).join(' '));
+  },
+
+  toString: function() {
+    return $A(this).join(' ');
+  }
+};
+
+Object.extend(Element.ClassNames.prototype, Enumerable);
+
+/*--------------------------------------------------------------------------*/
+
+Element.addMethods();
\ No newline at end of file

Added: examples/trunk/seamspace/war/src/main/webapp/lightbox/scriptaculous.js
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/lightbox/scriptaculous.js	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/lightbox/scriptaculous.js	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,58 @@
+// script.aculo.us scriptaculous.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
+
+// Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+// 
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+// For details, see the script.aculo.us web site: http://script.aculo.us/
+
+var Scriptaculous = {
+  Version: '1.8.1',
+  require: function(libraryName) {
+    // inserting via DOM fails in Safari 2.0, so brute force approach
+    document.write('<script type="text/javascript" src="'+libraryName+'"><\/script>');
+  },
+  REQUIRED_PROTOTYPE: '1.6.0',
+  load: function() {
+    function convertVersionString(versionString){
+      var r = versionString.split('.');
+      return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
+    }
+ 
+    if((typeof Prototype=='undefined') || 
+       (typeof Element == 'undefined') || 
+       (typeof Element.Methods=='undefined') ||
+       (convertVersionString(Prototype.Version) < 
+        convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
+       throw("script.aculo.us requires the Prototype JavaScript framework >= " +
+        Scriptaculous.REQUIRED_PROTOTYPE);
+    
+    $A(document.getElementsByTagName("script")).findAll( function(s) {
+      return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/))
+    }).each( function(s) {
+      var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,'');
+      var includes = s.src.match(/\?.*load=([a-z,]*)/);
+      (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
+       function(include) { Scriptaculous.require(path+include+'.js') });
+    });
+  }
+}
+
+Scriptaculous.load();
\ No newline at end of file

Added: examples/trunk/seamspace/war/src/main/webapp/pictures.xhtml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/pictures.xhtml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/pictures.xhtml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,77 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:f="http://java.sun.com/jsf/core"
+    xmlns:s="http://jboss.com/products/seam/taglib">
+
+  <ui:composition template="template.xhtml">
+    <ui:define name="head">
+      <script type="text/javascript" src="lightbox/prototype.js"></script>
+      <script type="text/javascript" src="lightbox/scriptaculous.js?load=effects,builder"></script>
+      <script type="text/javascript" src="lightbox/lightbox.js"></script>    
+      <link rel="stylesheet" href="lightbox/lightbox.css" type="text/css" media="screen" />
+    </ui:define>
+  
+    <ui:define name="content">
+    
+      <script type="text/javascript">
+        function confirmDelete()
+        {
+          return confirm("Are you sure you wish to delete this image? This action cannot be undone.");
+        }
+      </script>    
+      
+      <div class="errors"><h:messages globalOnly="true"/></div>     
+
+      <s:div rendered="#{selectedMember == null}">
+        Sorry, but this member does not exist.
+      </s:div>
+    
+      <s:div rendered="#{selectedMember != null}">
+
+        <h1>#{selectedMember.memberName}'s pictures</h1>
+
+        <div class="memberPictureCard">
+	        <s:link view="/profile.seam" propagation="none">
+	          #{selectedMember.memberName}<br/>
+              <h:graphicImage value="/content/images?id=#{selectedMember.picture.imageId}&amp;width=90"/>	          
+	        </s:link>                 
+	        
+          <s:span rendered="#{s:hasPermission(selectedMember, 'uploadImage')}">
+            [<s:link view="/pictureupload.xhtml" action="#{pictureAction.uploadPicture}" value="Upload picture" propagation="none"/>]
+          </s:span>	        
+	                  
+          <br style="clear:both"/>         
+        </div>
+
+        <div class="memberPictures">                    
+          <ui:repeat value="#{memberImages}" var="img">
+  
+            <div class="thumbnail">
+              <a href="content/images?id=#{img.imageId}" rel="lightbox[pictureset]" title="#{img.caption}">
+                <h:graphicImage value="/content/images?id=#{img.imageId}&amp;width=90" border="0"/>
+              </a>
+              <s:button view="/imagepermissions.seam" 
+                action="#{permissionSearch.search(pictureSearch.lookupImage())}" 
+                styleClass="padlock"
+                rendered="#{s:hasPermission(img, 'seam.grant-permission')}">
+                <f:param name="imageId" value="#{img.imageId}"/>
+              </s:button>
+              <s:button styleClass="trash" 
+                action="#{pictureSearch.delete(pictureSearch.lookupImage())}"
+                rendered="#{s:hasPermission(img, 'delete')}"
+                onclick="if (!confirmDelete()) return false">
+                <f:param name="imageId" value="#{img.imageId}"/>
+              </s:button>
+            </div>
+              
+          </ui:repeat> 
+        </div>
+
+      </s:div>                
+          
+    </ui:define>
+    
+  </ui:composition>
+</html>

Added: examples/trunk/seamspace/war/src/main/webapp/pictureupload.xhtml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/pictureupload.xhtml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/pictureupload.xhtml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,52 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:f="http://java.sun.com/jsf/core"
+    xmlns:s="http://jboss.com/products/seam/taglib">
+
+  <ui:composition template="template.xhtml">
+    <ui:define name="content">
+      <div id="contentMain">
+        <div class="errors"><h:messages globalOnly="true"/></div> 
+        
+        <h3>Upload Picture</h3>
+        
+        <div>
+
+          <h:form styleClass="register" enctype="multipart/form-data">
+            <s:validateAll>
+
+              <div class="formRow">
+                <h:outputLabel for="caption">Caption</h:outputLabel>
+                <h:inputText id="caption" value="#{pictureAction.memberImage.caption}"/>
+                <div class="validationError"><h:message for="caption"/></div>
+              </div>               
+
+              
+              <div class="formRow">
+                <h:outputLabel for="picture">Picture</h:outputLabel>
+                <s:fileUpload id="picture" data="#{pictureAction.memberImage.data}"
+                              contentType="#{pictureAction.memberImage.contentType}" />
+                <div class="validationError"><h:message for="picture"/></div>
+              </div>               
+
+            </s:validateAll>
+            
+            <div class="buttons">
+              <h:commandButton value="Upload" action="#{pictureAction.savePicture}" styleClass="formButton"/>
+              <s:button value="Cancel" propagation="end" view="/pictures.xhtml" styleClass="formButton">
+                <f:param name="name" value="#{authenticatedMember.memberName}"/>
+              </s:button>
+            </div>
+          
+          </h:form>
+          
+          <br class="clear"/>
+        </div>
+      </div>
+     
+    </ui:define>
+    
+  </ui:composition>
+</html>

Added: examples/trunk/seamspace/war/src/main/webapp/profile.xhtml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/profile.xhtml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/profile.xhtml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,117 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:f="http://java.sun.com/jsf/core"
+    xmlns:s="http://jboss.com/products/seam/taglib">
+
+  <ui:composition template="template.xhtml">
+    <ui:define name="content">
+      
+      <div class="errors"><h:messages globalOnly="true"/></div>     
+
+      <s:div rendered="#{selectedMember == null}">
+        Sorry, but this member does not exist.
+      </s:div>
+    
+      <s:div rendered="#{selectedMember != null}">
+      
+        <s:div id="memberCard">
+          <h1>#{selectedMember.memberName}'s profile</h1>
+                    
+          <s:div id="memberCardPicture" rendered="#{selectedMember.picture ne null}">
+            <h:graphicImage value="/content/images?id=#{selectedMember.picture.imageId}&amp;width=170"/>
+          </s:div>
+          
+          <div id="memberCardText">
+            <span class="tagline">"#{selectedMember.tagline}"</span><br/><br/>
+            #{selectedMember.gender.descr}<br/>
+            #{selectedMember.age}<br/>
+            #{selectedMember.location}<br/>
+          </div>
+          
+          <br style="clear:both"/>
+          
+          View My: 
+          <s:link view="/pictures.xhtml" value="Pics">
+            <f:param name="name" value="#{selectedMember.memberName}"/>
+          </s:link>
+          
+        </s:div>
+        
+        <s:div id="memberBlog">
+          <div class="sectionHeader">#{selectedMember.memberName}'s latest blog entries</div>
+          
+          <ui:repeat value="#{profile.latestBlogs}" var="latestBlog">
+            <div class="blogSummary">#{latestBlog.title} 
+              (<s:link view="/blogentry.seam" value="view more">
+                 <f:param name="name" value="#{selectedMember.memberName}"/>
+                 <f:param name="blogId" value="#{latestBlog.blogId}"/>
+               </s:link>)
+            </div>
+          </ui:repeat>            
+          
+          [<s:link id="viewBlog" view="/blog.seam" value="View all blog entries" propagation="none">
+             <f:param name="name" value="#{selectedMember.memberName}"/>
+           </s:link>]
+          
+          <s:span rendered="#{s:hasPermission(selectedMember, 'createBlog')}">
+            [<s:link id="createBlog" action="#{blog.createEntry}" value="Create new blog entry" propagation="none"/>]
+          </s:span>
+        </s:div>
+        
+        <s:div id="memberFriends">
+          <div class="sectionHeader">#{selectedMember.memberName}'s friends</div>
+          
+          <ui:repeat value="#{profile.friends}" var="f">
+            <div class="friend">
+              
+              <s:link view="/profile.seam" propagation="none">
+                <f:param name="name" value="#{f.memberName}"/>
+                #{f.memberName}<br/>
+                <h:graphicImage value="/content/images?id=#{f.picture.imageId}&amp;width=90"/>                
+              </s:link>
+              
+            </div>          
+          </ui:repeat>          
+          
+          <br class="clear"/>
+        
+          <s:span rendered="#{selectedMember.memberId != authenticatedMember.memberId and s:hasPermission(selectedMember, 'createFriendRequest')}">
+            [<s:link view="/friendrequest.seam" value="Send a friend request" propagation="none"/>]
+          </s:span>
+          
+        </s:div>
+        
+        <s:div id="friendComments">
+          <div class="sectionHeader">#{selectedMember.memberName}'s friend's comments</div>
+          
+	        <ui:repeat value="#{profile.friendComments}" var="c">
+	          <table class="friendComments">
+	            <tr>
+		            <td class="friendCommentor">					        
+					        <s:link view="/profile.seam">
+					          <f:param name="name" value="#{c.friend.memberName}"/>
+					          #{c.friend.memberName}<br/>
+                        <h:graphicImage value="/content/images?id=#{c.friend.picture.imageId}&amp;width=90"/>
+					        </s:link>
+		            </td>
+		            
+		            <td style="text-align: left">
+                  <b>#{c.formattedCommentDate}</b><br/>
+		              <p><s:formattedText value="#{c.comment}"/></p>
+		            </td>	            
+	            </tr>
+	          </table>	          	          
+	        </ui:repeat>            
+          
+          <s:span rendered="#{s:hasPermission(selectedMember, 'createFriendComment')}">
+            [<s:link view="/friendcomment.seam" value="Add Comment"/>]
+          </s:span>          
+        </s:div>        
+      </s:div>                
+          
+    </ui:define>
+    
+  </ui:composition>
+</html>

Added: examples/trunk/seamspace/war/src/main/webapp/register.xhtml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/register.xhtml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/register.xhtml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,103 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:f="http://java.sun.com/jsf/core"
+    xmlns:s="http://jboss.com/products/seam/taglib"
+    xmlns:rich="http://richfaces.org/rich">
+
+  <ui:composition template="template.xhtml">
+    <ui:define name="content">
+      <div id="contentMain">
+        <div class="errors"><h:messages globalOnly="true"/></div> 
+        
+        <p>
+          Already a member? <s:link view="/home.seam" value="Click here to log in" propagation="none"/>
+        </p>        
+        
+        <div id="register">
+          <div class="registerHeader">
+            JOIN SEAMSPACE HERE!
+          </div>
+                   
+          <h:form styleClass="register">
+            <s:validateAll>
+              <div class="formRow">
+                <h:outputLabel for="email">Email address<em>*</em></h:outputLabel>
+                <h:inputText id="email" value="#{register.member.email}" required="true" styleClass="wide"/>
+                <div class="validationError"><h:message for="email"/></div>
+              </div>
+
+              <div class="formRow">
+                <h:outputLabel for="firstName">First name<em>*</em></h:outputLabel>
+                <h:inputText id="firstName" value="#{register.member.firstName}" required="true"/>
+                <div class="validationError"><h:message for="firstName"/></div>
+              </div>
+              
+              <div class="formRow">
+                <h:outputLabel for="lastName">Last name<em>*</em></h:outputLabel>
+                <h:inputText id="lastName" value="#{register.member.lastName}" required="true"/>
+                <div class="validationError"><h:message for="lastName"/></div>
+              </div>    
+              
+              <div class="formRow">
+                <h:outputLabel for="memberName">Nick name<em>*</em></h:outputLabel>
+                <h:inputText id="memberName" value="#{register.member.memberName}" required="true"/>
+                <div class="validationError"><h:message for="memberName"/></div>
+              </div>
+              
+              <div class="formRow">
+                <h:outputLabel for="username">Username<em>*</em></h:outputLabel>
+                <h:inputText id="username" value="#{register.username}" required="true"/>
+                <div class="validationError"><h:message for="username"/></div>
+              </div>
+              
+              <div class="formRow">
+                <h:outputLabel for="password">Password<em>*</em></h:outputLabel>
+                <h:inputSecret id="password" value="#{register.password}" required="true"/>
+                <div class="validationError"><h:message for="password"/></div>
+              </div>   
+              
+              <div class="formRow">
+                <h:outputLabel for="confirmPassword">Confirm password<em>*</em></h:outputLabel>
+                <h:inputSecret id="confirmPassword" value="#{register.confirm}" required="true"/>
+                <div class="validationError"><h:message for="confirmPassword"/></div>
+              </div>         
+              
+              <div class="formRow">
+                <h:outputLabel for="gender">Gender<em>*</em></h:outputLabel>
+                <h:selectOneRadio id="gender" value="#{register.gender}" required="true">
+                  <f:selectItem itemValue="Male" itemLabel="Male" />
+                  <f:selectItem itemValue="Female" itemLabel="Female" />
+                </h:selectOneRadio>
+                <div class="validationError"><h:message for="gender"/></div>
+              </div>                                                   
+
+              <div class="formRow">
+                <h:outputLabel for="dob">Date of birth<em>*</em></h:outputLabel>
+               	<rich:calendar id="dob" value="#{register.member.dob}" required="true" datePattern="MM/dd/yyyy" buttonIcon="images/ellipsis.png" />
+                <div class="validationError"><h:message for="dob"/></div>
+              </div>           
+              
+              <div class="formRow">
+                <h:outputLabel for="verifyCaptcha"><h:graphicImage value="/seam/resource/captcha"/><em>*</em></h:outputLabel>
+                <h:inputText id="verifyCaptcha" value="#{captcha.response}" required="true"/>
+                <div class="validationError"><h:message for="verifyCaptcha"/></div>
+              </div>
+              
+            </s:validateAll>
+            
+            <div class="buttons">
+              <h:commandButton value="Next" action="#{register.next}" styleClass="registerButton"/>            
+            </div>
+          
+          </h:form>
+          
+          <br class="clear"/>
+        </div>
+      </div>
+     
+    </ui:define>
+    
+  </ui:composition>
+</html>

Added: examples/trunk/seamspace/war/src/main/webapp/register2.xhtml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/register2.xhtml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/register2.xhtml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,42 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:s="http://jboss.com/products/seam/taglib">
+
+  <ui:composition template="template.xhtml">
+    <ui:define name="content">
+      <div id="contentMain">
+        <div class="errors"><h:messages globalOnly="true"/></div> 
+        
+        <div id="register">
+          <div class="registerHeader">
+            JOIN SEAMSPACE HERE!
+          </div>
+          
+          <h:form styleClass="register" enctype="multipart/form-data">
+            <s:validateAll>
+              
+              <div class="formRow">
+                <h:outputLabel for="picture">Member photo</h:outputLabel>
+                <s:fileUpload id="picture" data="#{register.picture}" accept="image/png"
+                              contentType="#{register.pictureContentType}" />
+                <div class="validationError"><h:message for="picture"/></div>
+              </div>               
+
+            </s:validateAll>
+            
+            <div class="buttons">
+              <h:commandButton value="Upload" action="#{register.uploadPicture}" styleClass="registerButton"/>            
+            </div>
+          
+          </h:form>
+          
+          <br class="clear"/>
+        </div>
+      </div>
+     
+    </ui:define>
+    
+  </ui:composition>
+</html>

Added: examples/trunk/seamspace/war/src/main/webapp/roledetail.page.xml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/roledetail.page.xml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/roledetail.page.xml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,9 @@
+<page xmlns="http://jboss.com/products/seam/pages"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.1.xsd">
+    <navigation from-action="#{roleAction.save}">
+        <rule if-outcome="success">
+            <redirect view-id="/rolemanager.xhtml"/>
+        </rule>
+    </navigation>
+</page>

Added: examples/trunk/seamspace/war/src/main/webapp/roledetail.xhtml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/roledetail.xhtml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/roledetail.xhtml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,55 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:f="http://java.sun.com/jsf/core"
+    xmlns:rich="http://richfaces.org/rich"       
+    xmlns:s="http://jboss.com/products/seam/taglib">
+
+  <ui:composition template="template.xhtml">
+  
+    <ui:define name="head">
+      <link href="style/security.css" rel="stylesheet" type="text/css"/>
+    </ui:define>  
+   
+    <ui:define name="content">
+        
+      <div id="contentMain">
+
+  	    <h2>Role Details</h2>  
+  	    
+  	    <h:messages globalOnly="true"/>
+  	    
+  	    <h:form id="role">
+              
+          <div class="formRow">
+            <h:outputLabel for="role" value="Role" styleClass="formLabel"/>
+            <h:inputText id="name" value="#{roleAction.role}" readonly="#{identityManager.userExists(roleAction.role)}"/>
+            <div class="validationError"><h:message for="role"/></div>
+          </div>              
+
+          <div class="formRow">            
+            <h:outputLabel for="groups" value="Member of" styleClass="formLabel"/>
+            <div class="selectMany">
+              <h:selectManyCheckbox id="roles" value="#{roleAction.groups}" layout="pageDirection" styleClass="roles">
+                <s:selectItems value="#{roleAction.assignableRoles}" var="role" label="#{role}"/>
+              </h:selectManyCheckbox>
+            </div>
+            <div class="validationError"><h:message for="groups"/></div>            
+          </div>
+          
+          <div class="formButtons">
+            <h:commandButton id="save" value="Save" action="#{roleAction.save}" styleClass="formButton"/>
+            <s:button id="cancel" view="/rolemanager.xhtml" value="Cancel" propagation="end" styleClass="formButton"/>
+          </div>
+    
+          <br class="clear"/>
+  	    
+  	    </h:form>
+
+	    </div>
+	    
+    </ui:define>
+    
+  </ui:composition>
+</html>

Added: examples/trunk/seamspace/war/src/main/webapp/rolemanager.page.xml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/rolemanager.page.xml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/rolemanager.page.xml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,14 @@
+<page xmlns="http://jboss.com/products/seam/pages"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.1.xsd"
+    action="#{roleSearch.loadRoles}">
+  <restrict>#{s:hasPermission('seam.role', 'read')}</restrict>    
+  
+  <navigation from-action="#{roleAction.createRole}">
+    <redirect view-id="/roledetail.xhtml"/>
+  </navigation>
+  
+  <navigation from-action="#{roleAction.editRole(roleSearch.selectedRole)}">
+    <redirect view-id="/roledetail.xhtml"/>
+  </navigation>          
+</page>

Added: examples/trunk/seamspace/war/src/main/webapp/rolemanager.xhtml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/rolemanager.xhtml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/rolemanager.xhtml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,61 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:f="http://java.sun.com/jsf/core"    
+    xmlns:s="http://jboss.com/products/seam/taglib">
+
+  <ui:composition template="template.xhtml">
+   
+    <ui:define name="head">
+      <link href="style/security.css" rel="stylesheet" type="text/css"/>
+    </ui:define>   
+   
+    <ui:define name="content">
+        
+      <script type="text/javascript">
+        function confirmDelete()
+        {
+          return confirm("Are you sure you wish to delete this role? This action cannot be undone.");
+        }
+      </script>
+
+      <div id="contentMain">
+
+  	    <h2>Role Manager</h2>  
+  	    
+        <s:button id="newRole" action="#{roleAction.createRole}" styleClass="newrole" rendered="#{s:hasPermission('seam.account', 'create', null)}"/>
+  	    
+        <h:dataTable 
+            id="threads"
+            value="#{roles}" 
+            var="role" 
+            styleClass="security"
+            cellspacing="0"
+            headerClass="header"
+            rowClasses="odd,even"
+            columnClasses=",,action">
+          <h:column width="auto">
+            <f:facet name="header">Role</f:facet>
+            #{role}
+          </h:column>
+          <h:column id="roles" width="auto">
+            <f:facet name="header">Member Of</f:facet>
+            #{roleSearch.getRoleGroups(role)}
+          </h:column>
+          <h:column width="auto">
+            <f:facet name="header">Action</f:facet>          
+            <s:fragment rendered="#{s:hasPermission('seam.role', 'update')}">
+              <s:link id="edit" value="Edit" action="#{roleAction.editRole(roleSearch.selectedRole)}"/><span> | </span>
+            </s:fragment>
+            <s:link id="delete" value="Delete" action="#{identityManager.deleteRole(roleSearch.selectedRole)}"
+                    rendered="#{s:hasPermission('seam.role', 'delete')}"
+                    onclick="return confirmDelete()"/>              
+          </h:column>
+  	    </h:dataTable>
+	    </div>
+	    
+    </ui:define>
+    
+  </ui:composition>
+</html>

Added: examples/trunk/seamspace/war/src/main/webapp/security.xhtml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/security.xhtml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/security.xhtml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,28 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:f="http://java.sun.com/jsf/core"
+    xmlns:s="http://jboss.com/products/seam/taglib">
+
+  <ui:composition template="template.xhtml">
+   
+    <ui:define name="head">
+      <link href="style/security.css" rel="stylesheet" type="text/css"/>
+    </ui:define>
+   
+    <ui:define name="content">
+
+      <div id="contentMain">
+
+  	    <h2>Security</h2>  
+  	    
+        <s:button id="manageUsers" view="/usermanager.xhtml" styleClass="manageusers" value="Manage Users"/><br/>
+        <s:button id="manageRoles" view="/rolemanager.xhtml" styleClass="manageroles" value="Manage Roles"/>  	    
+
+	    </div>
+	    
+    </ui:define>
+    
+  </ui:composition>
+</html>

Added: examples/trunk/seamspace/war/src/main/webapp/security_error.xhtml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/security_error.xhtml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/security_error.xhtml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,17 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:s="http://jboss.com/products/seam/taglib">
+
+  <ui:composition template="template.xhtml">
+    <ui:define name="content">
+      
+      <h1>A Security Error has occurred</h1>
+      
+      <div class="errors"><h:messages globalOnly="true"/></div>                      
+          
+    </ui:define>
+    
+  </ui:composition>
+</html>

Added: examples/trunk/seamspace/war/src/main/webapp/style/advertising.png
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/style/advertising.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/style/btn_newpermission.png
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/style/btn_newpermission.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/style/btn_newrole.png
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/style/btn_newrole.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/style/btn_newuser.png
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/style/btn_newuser.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/style/cal-next.png
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/style/cal-next.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/style/cal-prev.png
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/style/cal-prev.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/style/date.css
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/style/date.css	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/style/date.css	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,185 @@
+div.seam-date 
+{
+  margin-top: 5px;
+  border: 1px solid #AAAAAA;
+  background: #fff url(../img/input.bg.gif) 0 0 repeat-x;
+  background-color: #FFFFFF;  
+  color: #505050;
+  font-family: Tahoma, Arial, Helvetica, sans-serif;
+  font-size: 12px;
+}
+
+table.seam-date td {
+  font-family: Tahoma, Arial, Helvetica, sans-serif;
+  font-weight: 11px; 
+}
+
+.seam-date-monthNames
+{
+  width: 70px;
+  border: 1px solid #dddddd;
+  border-right: 3px solid #444444;
+  border-bottom: 3px solid #444444;
+  background-color: #ffffff; 
+  font-size: 12px;
+  cursor: pointer;	
+  font-family: Tahoma, Arial, Helvetica, sans-serif;
+  font-weight: normal;
+}
+
+a.seam-date-monthNameLink, a.seam-date-monthNameLink:visited
+{
+  text-align: center;
+  display: block;
+  color: #555555;  
+}
+
+a.seam-date-monthNameLink:hover
+{ 
+  background-color: #CCCCCC;
+  color: red;  
+}
+
+.seam-date-years
+{
+  height: 10em;
+  overflow: auto;
+  width: 60px;
+  border: 1px solid #dddddd;
+  border-right: 3px solid #444444;
+  border-bottom: 3px solid #444444;
+  background-color: #ffffff; 
+  font-size: 12px;
+  cursor: pointer;	
+  font-family: Tahoma, Arial, Helvetica, sans-serif;
+  font-weight: normal;
+}
+
+a.seam-date-yearLink, a.seam-date-yearLink:visited
+{
+  text-align: center;
+  display: block;
+  color: #555555;    
+}
+
+a.seam-date-yearLink:hover
+{
+  background-color: #CCCCCC;
+  color: red;    
+}  
+
+tr.seam-date-header
+{
+  padding: 2px 0px 2px 0px;
+}
+  
+td.seam-date-header
+{
+  padding: 0px 8px 0px 8px;
+  text-align: center;
+  color: gray;
+  font-family: Tahoma, Arial, Helvetica, sans-serif;
+  font-weight: bold;
+  font-size: 12px;  
+}
+
+td.seam-date-header-prevMonth
+{
+  background-image: url("cal-prev.png");
+  background-repeat: no-repeat;
+  background-position: center;
+  padding: 0px 2px 0px 2px;
+  width: 17px;
+  height: 16px;
+  margin-left: 2px;
+}
+
+td.seam-date-header-nextMonth
+{
+  background-image: url("cal-next.png");
+  background-repeat: no-repeat;
+  background-position: center;
+  padding: 0px 2px 0px 2px;
+  width: 17px;
+  height: 16px;
+  margin-right: 2px;
+}
+
+tr.seam-date-headerDays
+{
+  color: white;
+  font-weight: normal;
+}
+
+tr.seam-date-headerDays > td
+{
+  background-color: #CCCCCC;
+  border: 1px solid #AAAAAA;
+  color: white;
+  text-align: center;
+  width: 26px;   
+}
+
+tr.seam-date-footer
+{
+  background-color: white; 
+  color: #505050;
+  font-weight: bold;
+}
+
+tr.seam-date-footer > td
+{
+  text-align: center;
+}
+
+td.seam-date-inMonth
+{
+  background-color: white; 
+  color: black;
+  font-weight: normal;
+  cursor: pointer;
+  border: 1px solid #ece9d8;
+}
+
+td.seam-date-outMonth
+{
+  background-color: white; 
+  color: #999999;
+  font-weight: normal;
+  cursor: pointer;
+  border: 1px solid #ece9d8;
+}
+
+td.seam-date-selected
+{
+  background-color: #CCCCCC;        
+  border: 1px solid #AAAAAA; 
+  color: black;
+  font-weight: normal;
+}
+
+td.seam-date-dayOff-inMonth
+{
+  background-color: #efefef;
+  color: black;
+  font-weight: normal;
+  cursor: pointer;
+  border: 1px solid #ece9d8;
+}
+
+td.seam-date-dayOff-outMonth
+{
+  background-color: #efefef;
+  color: #999999;
+  font-weight: normal;
+  cursor: pointer;
+  border: 1px solid #ece9d8;
+}
+
+td.seam-date-hover 
+{
+  background-color: #CCCCCC;
+  border: 1px solid #AAAAAA;
+  cursor: pointer;
+  color: red;
+}
\ No newline at end of file

Added: examples/trunk/seamspace/war/src/main/webapp/style/divider.png
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/style/divider.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/style/manage_roles.png
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/style/manage_roles.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/style/manage_users.png
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/style/manage_users.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/style/padlock.png
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/style/padlock.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/style/seamspace.css
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/style/seamspace.css	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/style/seamspace.css	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,600 @@
+body {
+  font-family: verdana, arial, sans-serif, helvetica;
+  background-color: #e5e5e5;
+  font-size:11px;
+  margin: 0;
+}
+
+h1, h2, h3 {
+  margin-top: 2px;
+  margin-bottom: 6px;
+}
+
+#header {
+  margin-left: auto;
+  margin-right: auto;
+  background: url(seamspace.png) left top no-repeat;
+  height: 100px;
+  width: 800px;
+  background-color: #003399;
+}
+
+#menubar {
+  margin-left: auto;
+  margin-right: auto;
+  overflow: none;
+  height: 17px;
+  width: 800px;
+  background-color: #5a8cc0;
+  text-align: center;
+  padding-top: 3px;
+}
+
+#content {
+  padding-top: 10px;
+  margin-left: auto;
+  margin-right: auto;
+  width: 800px;
+  background-color: #ffffff;
+  min-height: 800px;
+}
+
+#content a, #content a:visited {
+  font-weight: bold;
+  color: #003399;
+}
+
+#content a:hover {
+  font-weight: bold;
+  color: #7799dd;
+}
+
+#contentMain {
+  float: left;
+  width: 430px;
+  margin-left: 10px;
+  margin-top: 10px;
+}
+
+#contentDivider {
+  float: left;
+  background: url(divider.png) repeat-y top left;
+  height: 100%;
+  width: 10px;
+}
+
+#contentSide {
+  float: left;
+  width: 310px;
+  margin-left: 10px;
+  margin-top: 10px;
+  margin-right: 10px;
+}
+
+#memberCard {
+  width: 350px;
+  float: left;
+  margin-left: 10px;
+  border: 2px solid #eeeeee;
+  background-color: #f5f5f5;
+  padding: 2px;
+}
+
+#memberCardPicture {
+  width: 170px;
+  float: left;
+}
+
+#memberCardText {
+  width: 170px;
+  float: right;
+}
+
+#memberBlog {
+  width: 400px;
+  float: right;
+  margin-right: 10px;
+  border: 2px solid #eeeeee;
+  padding: 2px;
+}
+
+#blogMemberCard {
+  width: 140px;
+  float: left;
+  margin-left: 10px;
+  border: 2px solid #eeeeee;
+  background-color: #f5f5f5;
+  padding: 2px;
+  text-align: center;
+}
+
+#blog {
+  float: right;
+  width: 600px;
+  margin-right: 10px;
+  padding: 2px;  
+}
+
+#memberFriends {
+  width: 400px;
+  float: right;
+  margin-right: 10px;
+  margin-top: 12px;
+  border: 2px solid #eeeeee;
+  padding: 2px;
+}
+
+div.friend {
+  float: left;
+  width: 99px;
+  text-align: center;
+}
+
+#friendComments {
+  width: 400px;
+  float: right;
+  margin-right: 10px;
+  margin-top: 12px;
+  border: 2px solid #eeeeee;
+  padding: 2px;
+}
+
+table.friendComments {
+  width: 100%;
+}
+
+table.friendComments td {
+  border: 1px solid #dddddd;
+  background-color: #eeeeee;
+  text-align: center;
+  vertical-align: top;
+}
+
+td.friendCommentor {
+  width: 100px;
+}
+
+div.friendRequest {
+  background-color: #bbddff;
+  width: 600px;
+  float: right;
+  margin-right: 10px;
+  border: 2px solid #eeeeee;
+  padding: 4px;
+}
+
+div.friendRequest textarea {
+  font-family: verdana, arial, sans-serif, helvetica;
+  font-size:11px;
+  margin: 4px 12px 8px 0px;
+  width: 560px;
+  height: 120px;
+}
+
+div.blogSummary {
+  padding: 0px 2px 4px 2px;
+  margin-bottom: 8px;
+}
+
+div.blogEntry {
+  background-color: #bbddff;
+  padding: 8px 8px 8px 8px;
+  margin-bottom: 12px;
+}
+
+div.blogEntry label {
+  font-weight: bold;
+}
+
+div.blogEntry input.title {
+  width: 560px;
+}
+
+div.blogEntry textarea {
+  font-family: verdana, arial, sans-serif, helvetica;
+  font-size:11px;
+  margin: 4px 12px 8px 0px;
+  width: 560px;
+  height: 120px;
+}
+
+div.blogDate {
+  font-weight: bold;
+  margin-bottom: 8px;
+}
+
+div.blogTitle {
+  font-weight: bold;
+  margin-left: 24px;
+  margin-bottom: 8px;
+}
+
+div.blogText {
+  margin-left: 24px;
+  margin-bottom: 8px;
+  text-align: justify;
+}
+
+div.blogFooter {
+  margin-bottom: 8px;
+}
+
+table.blogComment {
+  margin-bottom: 2px;
+  width: 100%;
+}
+
+td.blogCommentor {
+  width: 160px;
+  background-color: #ff9933;
+  text-align: center;
+  vertical-align: top;
+}
+
+td.blogCommentText {
+  background-color: #ffddbb;
+  padding: 12px 4px 4px 8px;
+}
+
+div.commentEntry {
+  background-color: #ff9933;
+  padding: 4px 4px 4px 4px;
+}
+
+div.commentEntry label {
+  font-weight: bold;
+  color: #ffffff;
+  margin-bottom: 4px;
+}
+
+div.commentEntry textarea {
+  font-family: verdana, arial, sans-serif, helvetica;
+  font-size:11px;
+  margin: 8px 12px 8px 6px;
+  width: 560px;
+  height: 120px;
+}
+
+div.buttons {
+  float: right;
+}
+
+input.action {
+  display: inline;
+  margin-right: 8px;
+  margin-top: 4px;
+  margin-bottom: 4px;  
+  border: 1px solid #003399;
+  background-color: #ffffff;
+  color: #003399;
+  font-size: 10px;
+  padding: 3px 8px 3px 8px;
+}
+
+div.headerRight {
+  float: right;
+  width: 440px;
+  padding-right: 4px;
+  padding-top: 4px;
+}
+
+div.headerMenu {
+  float: right;
+}
+
+div.advertising {
+  background: url(advertising.png) no-repeat top left;
+  width: 307px;
+  height: 86px;  
+}
+
+div.sectionHeader {
+  font-weight: bold;
+  font-size: normal;
+  background-color: #eeeeee;
+  padding: 2px 2px 2px 2px;
+  margin-bottom: 12px;
+}
+
+span.tagline {
+  font-style: italic;
+}
+
+a, a:visited, a:hover {
+  color: #ffffff;
+  text-decoration: none;
+  padding-left: 2px;
+  padding-right: 2px;
+}
+
+#header a:hover {
+  text-decoration: underline;
+}
+
+#search {
+  margin-top: 2px;
+}
+
+#search a:hover {
+  margin-bottom: 4px;
+  text-decoration: none;
+}
+
+input.searchField {
+  width: 290px;
+}
+
+input.searchButton {
+  width: 140px;
+}
+
+#menubar a:hover {
+  color: #003399;
+  text-decoration: underline;
+}
+
+div.memberLogin {
+  margin-top: 16px;
+  width: 300px;
+  border: 1px solid #003399;
+  padding: 0px;
+}
+
+div.loginHeader {
+  color: #ffffff;
+  font-weight: bold;
+  background-color: #003399;
+  height: 18px;
+  padding: 2px 2px 2px 2px;
+}
+
+div.newMembers {
+  width: 300px;
+  margin-top: 8px;
+  border: 1px solid #ffcc99;
+}
+
+div.newMembersHeader {
+  font-weight: bold;
+  background-color: #ffcc99;
+  height: 18px;
+  padding: 2px 2px 2px 2px;
+}
+
+div.newMember {
+  float: left;
+  width: 99px;
+  text-align: center;
+}
+
+div.newMember img {
+  border: none;
+}
+
+div.loginRow {
+  padding-top: 2px;
+  padding-bottom: 2px;
+}
+
+label.loginLabel {
+  float: left;
+  width: 100px; 
+  padding: 2px 2px 2px 2px;
+}
+
+div.buttons {
+  float: right;
+  margin-right: 60px;  
+}
+
+input.loginButton {
+  display: inline;
+  margin-right: 8px;
+  margin-top: 4px;
+  margin-bottom: 4px;  
+  border: 1px solid #003399;
+  background-color: #ffffff;
+  color: #003399;
+  font-size: 10px;
+  padding: 3px 8px 3px 8px;
+}
+
+input.registerButton {
+  display: inline;
+  margin-top: 4px;
+  margin-bottom: 4px;
+  border: 1px solid #ffa76d;
+  background-color: #ff6600;
+  color: #ffffff;
+  font-size: 10px;
+  padding: 3px 8px 3px 8px;
+}
+
+div.loginRow input[type='text'], div.loginRow input[type='password'] {
+  width: 180px;
+}
+
+.clear {
+	clear: both;
+	font-size: 0px;
+}
+
+.divider {
+  color: #aaaaaa;
+}
+
+div.errors {
+  color: #ff0000;
+  font-weight: bold;
+  font-size: normal;
+}
+
+div.errors ul {
+  list-style: none;
+}
+
+#register {
+  float: left;
+  width: 400px;
+  border: 1px solid #5a8cc0;
+}
+
+div.registerHeader {
+  font-size: normal;
+  font-weight: bold;
+  color: #ffffff;
+  background-color: #5a8cc0;
+  padding: 8px 4px 8px 12px;
+}
+
+form.register label {
+  float: left;
+  display: block;
+  vertical-align: top;
+  width: 120px;  
+  margin-top: 2px;
+}
+
+form.register input.wide {
+  width: 240px;
+}
+
+form.register em {
+  font-size: normal;
+  font-weight: bold;
+  color: #ff0000;
+}
+
+form.register div.validationError {
+  margin-left: 120px;
+  font-weight: bold;
+  color: #ff0000;
+}
+
+img.ellipsis {
+  width: 18px;
+  height: 18px;
+  border: none;
+  margin-bottom: 2px;
+  margin-left: 2px;
+  vertical-align: bottom;
+  cursor: pointer;
+}
+
+div.seam-date {
+  background-color: #ffffff;
+  border: 1px solid #5a8cc0;
+}
+
+td.seam-date-hover {
+  background-color: #5a8cc0;
+  color: #ffffff;
+  cursor: pointer;
+}
+
+quote {
+  border: 1px solid #fac289;
+  margin: 4px 8px 4px 8px;
+  padding: 12px 12px 12px 12px;
+  background-color: #fbf4ec;
+  display: block;
+}
+
+/* General form styles */
+
+div.formRow {
+  padding: 3px 4px 3px 2px;  
+  clear: both;
+}
+
+div.formRow label {
+  float: left;
+  width: 120px; 
+  padding: 2px 2px 2px 2px;
+}
+
+div.formRow input[type='text'] {
+  width: 120px;
+}
+
+div.formRow input[type='password'] {
+  width: 120px;
+}
+
+div.validationError {
+  margin-left: 120px;
+  font-weight: bold;
+  color: #ff0000;
+}
+
+.roles {
+  width: 120px;
+  border: 1px solid #7F9DB9;
+  background-color: #E7EDF7;
+}
+
+div.selectMany label {
+  float: none;
+}
+
+.formButton {
+  background: url(../images/bg_button.png) top left repeat-x;
+  border: 1px solid #003399;
+  font-size: small;
+  font-weight: bold;
+  color: #000000; 
+  margin-right: 8px;
+  padding-left: 4px;
+  padding-right: 4px;
+}
+
+div.formButtons {
+  float: right;
+  padding: 4px 8px 16px 2px;
+}
+
+div.memberPictures {
+  float: right;
+  width: 600px;
+  margin-right: 10px;
+  padding: 2px;  
+  background-color: #000000;
+  border: 1px solid #aaaaaa;
+  padding: 8px 8px 8px 8px;
+}
+
+div.memberPictureCard {
+  width: 140px;
+  float: left;
+  margin-left: 10px;
+  border: 2px solid #eeeeee;
+  background-color: #f5f5f5;
+  padding: 2px;
+  text-align: center;
+}
+
+div.thumbnail {
+  width: 110px;
+  background-color: #333333;
+  padding: 10px 10px 10px 10px;
+  margin: 8px 8px 8px 8px;
+  float: left;
+  text-align: center;
+}
+
+input.padlock {
+  background: url(padlock.png) top left no-repeat;
+  width: 14px;
+  height: 20px;
+  float: left;
+  border: 0px;
+  margin-left: 10px;
+}
+
+input.trash {
+  background: url(trash.png) top left no-repeat;
+  width: 20px;
+  height: 20px; 
+  float: left;
+  border: 0px;  
+}
\ No newline at end of file

Added: examples/trunk/seamspace/war/src/main/webapp/style/seamspace.png
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/style/seamspace.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/style/security.css
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/style/security.css	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/style/security.css	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,126 @@
+input.newuser {
+  background: url(btn_newuser.png) top left no-repeat;
+  height: 39px;
+  width: 113px;
+  margin: 4px 4px 4px 4px;
+  border: 0px;
+  cursor: pointer;  
+}
+
+input.newrole {
+  background: url(btn_newrole.png) top left no-repeat;
+  height: 39px;
+  width: 113px;
+  margin: 4px 4px 4px 4px;
+  border: 0px;
+  cursor: pointer;  
+}
+
+input.newpermission {
+  background: url(btn_newpermission.png) top left no-repeat;
+  height: 39px;
+  width: 113px;
+  margin: 4px 4px 4px 4px;
+  border: 0px;
+  cursor: pointer;  
+}
+
+input.manageusers {
+  display: block;
+  background: url(manage_users.png) top left no-repeat;
+  height: 88px;
+  width: 300px;
+  padding-left: 10px;
+  font-size: 19px;
+  font-weight: bold;
+  color: #333333;
+  border: 0px;
+  cursor: pointer;
+  margin-top: 20px;
+  margin-left: 20px;
+}
+
+input.manageroles {
+  display: block;
+  background: url(manage_roles.png) top left no-repeat;
+  height: 88px;
+  width: 300px;
+  padding-left: 10px;
+  font-size: 19px;
+  font-weight: bold;
+  color: #333333;
+  border: 0px;
+  cursor: pointer;  
+  margin-top: 20px;
+  margin-left: 20px;
+}
+
+.roles {
+  width: 120px;
+  border: 1px solid #7F9DB9;
+  background-color: #E7EDF7;
+}
+
+div.selectMany label {
+  float: none;
+}
+
+.formButton {
+  background: url(../images/bg_button.png) top left repeat-x;
+  border: 1px solid #003399;
+  font-size: small;
+  font-weight: bold;
+  color: #000000; 
+  margin-right: 8px;
+  padding-left: 4px;
+  padding-right: 4px;
+}
+
+div.formButtons {
+  float: right;
+  padding: 4px 8px 16px 2px;
+}
+
+div.checkmark {
+  background: url(../images/checkmark.png) top left no-repeat;
+  width: 14px;
+  height: 15px;
+  margin-left: auto;
+  margin-right: auto;    
+}
+
+div.cross {
+  background: url(../images/cross.png) top left no-repeat;
+  width: 14px;
+  height: 15px;
+  margin-left: auto;
+  margin-right: auto;
+}
+
+table.security {
+  border: 1px solid black;
+  width: 500px;
+}
+
+th.header {
+  background: url(table_header.png) top left repeat-x;
+  color: #ffffff;
+  padding-top: 3px;
+  padding-bottom: 3px;
+}
+
+tr.odd {
+  background-color: #ffffff;
+}
+
+tr.even {
+  background-color: #E9F5FF;
+}
+
+td.enabled {
+  text-align: center;
+}
+
+td.action {
+  text-align: right;
+}
\ No newline at end of file

Added: examples/trunk/seamspace/war/src/main/webapp/style/table_header.png
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/style/table_header.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/style/trash.png
===================================================================
(Binary files differ)


Property changes on: examples/trunk/seamspace/war/src/main/webapp/style/trash.png
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Added: examples/trunk/seamspace/war/src/main/webapp/template.xhtml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/template.xhtml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/template.xhtml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,66 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:s="http://jboss.com/products/seam/taglib"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:f="http://java.sun.com/jsf/core">
+
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+  <title>SeamSpace</title>
+  <link href="style/seamspace.css" rel="stylesheet" type="text/css"/>
+  <link href="style/date.css" rel="stylesheet" type="text/css"/>
+  <ui:insert name="head"/>
+</head>
+
+<body>
+
+  <div id="header">
+    <div class="headerRight">
+      <div class="headerMenu">
+
+        <s:fragment rendered="#{identity.loggedIn}">
+          <s:link id="profile" view="/profile.xhtml" value="My Profile" propagation="none">
+            <f:param name="name" value="#{authenticatedMember.memberName}"/>
+          </s:link>
+          <h:outputText styleClass="divider" value=" | "/>
+        </s:fragment>
+
+        <s:fragment rendered="#{s:hasRole('admin')}">
+          <s:link id="security" view="/security.xhtml" value="Security" propagation="none"/>
+          <h:outputText styleClass="divider" value=" | "/>
+        </s:fragment>
+        
+        <s:link id="logout" action="#{identity.logout}" value="Log out" rendered="#{identity.loggedIn}"/>
+        <h:outputLink id="login" value="home.seam" rendered="#{not identity.loggedIn}">Log in</h:outputLink>
+      </div>
+      <br style="clear:both"/>
+      <h:form>
+        <div>
+          <a href="#">SeamSpace</a><h:outputText styleClass="divider" value=" | "/>
+          <a href="#">People</a><h:outputText styleClass="divider" value=" | "/>
+          <a href="#">Music</a><h:outputText styleClass="divider" value=" | "/>
+          <a href="#">Blogs</a>
+        </div>
+        <div id="search">
+          <h:inputText type="text" styleClass="searchField"/>
+          <h:commandButton value="Search SeamSpace" onclick="javascript:alert('This feature coming soon!');return false" styleClass="searchButton"/>
+        </div>
+      </h:form>
+    </div>
+  </div>
+  
+  <div id="menubar">
+    <s:link view="/home.xhtml" value="Home" propagation="none"/><h:outputText styleClass="divider" value=" | "/>
+    <s:link value="Browse" onclick="javascript:alert('This feature coming soon!');return false"/><h:outputText styleClass="divider" value=" | "/>
+    <s:link value="Blog" onclick="javascript:alert('This feature coming soon!');return false"/><h:outputText styleClass="divider" value=" | "/>
+    <s:link value="Music" onclick="javascript:alert('This feature coming soon!');return false"/>
+  </div>
+
+  <div id="content">
+    <ui:insert name="content"/>
+  </div>
+   
+</body>
+</html>
+

Added: examples/trunk/seamspace/war/src/main/webapp/userdetail.page.xml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/userdetail.page.xml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/userdetail.page.xml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,9 @@
+<page xmlns="http://jboss.com/products/seam/pages"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.1.xsd">
+    <navigation from-action="#{userAction.save}">
+      <rule if-outcome="success">
+          <redirect view-id="/usermanager.xhtml"/>
+      </rule>
+    </navigation>
+</page>          

Added: examples/trunk/seamspace/war/src/main/webapp/userdetail.xhtml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/userdetail.xhtml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/userdetail.xhtml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,83 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:f="http://java.sun.com/jsf/core"
+    xmlns:rich="http://richfaces.org/rich"       
+    xmlns:s="http://jboss.com/products/seam/taglib">
+
+  <ui:composition template="template.xhtml">
+  
+    <ui:define name="head">
+      <link href="style/security.css" rel="stylesheet" type="text/css"/>
+    </ui:define>  
+   
+    <ui:define name="content">
+        
+      <div id="contentMain">
+
+  	    <h2>User Details</h2>  
+  	    
+  	    <h:messages globalOnly="true"/>
+  	    
+  	    <h:form id="user">
+  	    
+          <div class="formRow">
+            <h:outputLabel for="firstname" value="First name" styleClass="formLabel"/>
+            <h:inputText id="firstname" value="#{userAction.firstname}" readonly="#{identityManager.userExists(userAction.username)}"/>
+            <div class="validationError"><h:message for="firstname"/></div>
+          </div>  
+          
+          <div class="formRow">
+            <h:outputLabel for="lastname" value="Last name" styleClass="formLabel"/>
+            <h:inputText id="lastname" value="#{userAction.lastname}" readonly="#{identityManager.userExists(userAction.username)}"/>
+            <div class="validationError"><h:message for="lastname"/></div>
+          </div>            
+              
+          <div class="formRow">
+            <h:outputLabel for="username" value="Username" styleClass="formLabel"/>
+            <h:inputText id="username" value="#{userAction.username}" readonly="#{identityManager.userExists(userAction.username)}"/>
+            <div class="validationError"><h:message for="username"/></div>
+          </div>              
+
+          <div class="formRow">
+            <h:outputLabel for="password" value="Password" styleClass="formLabel"/>
+            <h:inputSecret id="password" value="#{userAction.password}"/>
+            <div class="validationError"><h:message for="password"/></div>
+          </div>              
+          
+          <div class="formRow">
+            <h:outputLabel for="confirm" value="Confirm password" styleClass="formLabel"/>
+            <h:inputSecret id="confirm" value="#{userAction.confirm}"/>
+          </div>                        
+              
+          <div class="formRow">            
+            <h:outputLabel for="roles" value="Member of" styleClass="formLabel"/>
+            <div class="selectMany">
+              <h:selectManyCheckbox id="roles" value="#{userAction.roles}" layout="pageDirection" styleClass="roles">
+                <s:selectItems value="#{identityManager.listGrantableRoles()}" var="role" label="#{role}"/>
+              </h:selectManyCheckbox>
+            </div>
+            <div class="validationError"><h:message for="roles"/></div>            
+          </div>
+          
+          <div class="formRow">
+            <h:outputLabel for="enabled" value="Account enabled" styleClass="formLabel"/>
+            <h:selectBooleanCheckbox id="enabled" value="#{userAction.enabled}"/>
+          </div>   
+                                      
+          <div class="formButtons">
+            <h:commandButton id="save" value="Save" action="#{userAction.save}" styleClass="formButton"/>
+            <s:button id="cancel" view="/usermanager.xhtml" value="Cancel" propagation="end" styleClass="formButton"/>
+          </div>
+    
+          <br class="clear"/>
+  	    
+  	    </h:form>
+
+	    </div>
+	    
+    </ui:define>
+    
+  </ui:composition>
+</html>

Added: examples/trunk/seamspace/war/src/main/webapp/usermanager.page.xml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/usermanager.page.xml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/usermanager.page.xml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,14 @@
+<page xmlns="http://jboss.com/products/seam/pages"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.1.xsd"
+    action="#{userSearch.loadUsers}">
+  <restrict>#{s:hasPermission('seam.user', 'read')}</restrict>    
+  
+  <navigation from-action="#{userAction.createUser}">
+    <redirect view-id="/userdetail.xhtml"/>
+  </navigation>
+  
+  <navigation from-action="#{userAction.editUser(userSearch.selectedUser)}">
+    <redirect view-id="/userdetail.xhtml"/>
+  </navigation>      
+</page>

Added: examples/trunk/seamspace/war/src/main/webapp/usermanager.xhtml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/usermanager.xhtml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/usermanager.xhtml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,74 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+    xmlns:ui="http://java.sun.com/jsf/facelets"
+    xmlns:h="http://java.sun.com/jsf/html"
+    xmlns:f="http://java.sun.com/jsf/core"
+    xmlns:s="http://jboss.com/products/seam/taglib">
+
+  <ui:composition template="template.xhtml">
+  
+    <ui:define name="head">
+      <link href="style/security.css" rel="stylesheet" type="text/css"/>
+    </ui:define>  
+   
+    <ui:define name="content">
+        
+      <script type="text/javascript">
+        function confirmDelete()
+        {
+          return confirm("Are you sure you wish to delete this user? This action cannot be undone.");
+        }
+      </script>
+
+      <div id="contentMain">
+
+  	    <h2>User Manager</h2>  
+  	    
+        <s:button id="newUser" action="#{userAction.createUser}" styleClass="newuser" rendered="#{s:hasPermission('seam.account', 'create')}"/>
+  	    
+        <h:dataTable 
+            id="threads"
+            value="#{users}" 
+            var="user" 
+            styleClass="security"
+            cellspacing="0"
+            headerClass="header"
+            rowClasses="odd,even"
+            columnClasses=",,enabled,action">
+          <h:column width="auto">
+            <f:facet name="header">
+              User name
+            </f:facet>
+            #{user}
+          </h:column>
+          <h:column width="auto">
+            <f:facet name="header">
+              Member Of
+            </f:facet>
+            #{userSearch.getUserRoles(user)}
+          </h:column>
+          <h:column id="enabled" width="auto">
+            <f:facet name="header">
+              Enabled
+            </f:facet>
+            <div class="#{identityManager.isUserEnabled(user) ? 'checkmark' : 'cross'}"/>
+          </h:column>
+          <h:column id="action" width="auto">
+            <f:facet name="header">
+              Action
+            </f:facet>
+          
+            <s:fragment rendered="#{s:hasPermission('seam.user', 'update')}">
+              <s:link id="edit" value="Edit" action="#{userAction.editUser(userSearch.selectedUser)}"/><span> | </span>
+            </s:fragment>
+            <s:link id="delete" value="Delete" action="#{identityManager.deleteUser(userSearch.selectedUser)}" 
+                    rendered="#{s:hasPermission('seam.user', 'delete')}"
+                    onclick="return confirmDelete()"/>
+          </h:column>
+  	    </h:dataTable>
+	    </div>
+	    
+    </ui:define>
+    
+  </ui:composition>
+</html>

Added: examples/trunk/seamspace/war/src/main/webapp/welcome.xhtml
===================================================================
--- examples/trunk/seamspace/war/src/main/webapp/welcome.xhtml	                        (rev 0)
+++ examples/trunk/seamspace/war/src/main/webapp/welcome.xhtml	2009-05-07 01:14:39 UTC (rev 10822)
@@ -0,0 +1,17 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+	  xmlns:ui="http://java.sun.com/jsf/facelets"
+	  xmlns:h="http://java.sun.com/jsf/html">
+
+<head>
+	<title>SeamSpace</title>
+</head>
+
+<body>
+  <h1>Login successful!</h1>
+
+  <div class="errors"><h:messages globalOnly="true"/></div> 
+
+</body>
+</html>
+




More information about the seam-commits mailing list