You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by jo...@apache.org on 2015/06/11 21:17:24 UTC

[47/50] [abbrv] struts-examples git commit: WW-4513 Move mailreader app to struts2-examples

WW-4513 Move mailreader app to struts2-examples


Project: http://git-wip-us.apache.org/repos/asf/struts-examples/repo
Commit: http://git-wip-us.apache.org/repos/asf/struts-examples/commit/0677ec5c
Tree: http://git-wip-us.apache.org/repos/asf/struts-examples/tree/0677ec5c
Diff: http://git-wip-us.apache.org/repos/asf/struts-examples/diff/0677ec5c

Branch: refs/heads/master
Commit: 0677ec5cc3db6583e8ddb582cde6d93170be0a6d
Parents: 198c654
Author: Johannes Geppert <jo...@apache.org>
Authored: Thu Jun 11 21:01:34 2015 +0200
Committer: Johannes Geppert <jo...@apache.org>
Committed: Thu Jun 11 21:01:34 2015 +0200

----------------------------------------------------------------------
 README.txt                                      |   23 -
 mailreader/README.txt                           |   23 +
 mailreader/pom.xml                              |   95 +
 mailreader/src/main/java/alternate.properties   |    3 +
 .../src/main/java/alternate_ja.properties       |    1 +
 mailreader/src/main/java/mailreader-default.xml |   47 +
 mailreader/src/main/java/mailreader-support.xml |   63 +
 .../java/mailreader2/ApplicationListener.java   |  234 ++
 .../mailreader2/AuthenticationInterceptor.java  |   52 +
 .../src/main/java/mailreader2/Constants.java    |  128 +
 .../main/java/mailreader2/Login-validation.xml  |   14 +
 mailreader/src/main/java/mailreader2/Login.java |   48 +
 .../src/main/java/mailreader2/Logout.java       |   35 +
 .../java/mailreader2/MailreaderSupport.java     |  583 +++++
 .../mailreader2/MailreaderSupport.properties    |   97 +
 .../mailreader2/MailreaderSupport_ja.properties |   89 +
 .../mailreader2/MailreaderSupport_ru.properties |   89 +
 ...egistration-Registration_save-validation.xml |   28 +
 .../mailreader2/Registration-validation.xml     |   32 +
 .../src/main/java/mailreader2/Registration.java |  122 +
 ...ubscription-Subscription_save-validation.xml |   23 +
 .../mailreader2/Subscription-validation.xml     |   11 +
 .../src/main/java/mailreader2/Subscription.java |  145 +
 .../src/main/java/mailreader2/Welcome.java      |   49 +
 mailreader/src/main/java/struts.xml             |   16 +
 mailreader/src/main/resources/LICENSE.txt       |  174 ++
 mailreader/src/main/resources/NOTICE.txt        |    5 +
 mailreader/src/main/resources/log4j2.xml        |   16 +
 .../src/main/resources/velocity.properties      |    1 +
 mailreader/src/main/webapp/META-INF/context.xml |    3 +
 mailreader/src/main/webapp/WEB-INF/database.xml |    9 +
 .../main/webapp/WEB-INF/jsp/ChangePassword.jsp  |   25 +
 .../src/main/webapp/WEB-INF/jsp/Error.jsp       |   40 +
 .../src/main/webapp/WEB-INF/jsp/Footer.jsp      |    6 +
 .../src/main/webapp/WEB-INF/jsp/Login.jsp       |   30 +
 .../src/main/webapp/WEB-INF/jsp/MainMenu.jsp    |   25 +
 .../main/webapp/WEB-INF/jsp/Registration.jsp    |  115 +
 .../main/webapp/WEB-INF/jsp/Subscription.jsp    |   60 +
 .../src/main/webapp/WEB-INF/jsp/Welcome.jsp     |   55 +
 mailreader/src/main/webapp/WEB-INF/web.xml      |   47 +
 mailreader/src/main/webapp/css/mailreader.css   |   46 +
 mailreader/src/main/webapp/index.html           |   10 +
 mailreader/src/main/webapp/struts-power.gif     |  Bin 0 -> 1798 bytes
 mailreader/src/main/webapp/tour.html            | 2470 ++++++++++++++++++
 pom.xml                                         |   95 -
 src/main/java/alternate.properties              |    3 -
 src/main/java/alternate_ja.properties           |    1 -
 src/main/java/mailreader-default.xml            |   47 -
 src/main/java/mailreader-support.xml            |   63 -
 .../java/mailreader2/ApplicationListener.java   |  234 --
 .../mailreader2/AuthenticationInterceptor.java  |   52 -
 src/main/java/mailreader2/Constants.java        |  128 -
 src/main/java/mailreader2/Login-validation.xml  |   14 -
 src/main/java/mailreader2/Login.java            |   48 -
 src/main/java/mailreader2/Logout.java           |   35 -
 .../java/mailreader2/MailreaderSupport.java     |  583 -----
 .../mailreader2/MailreaderSupport.properties    |   97 -
 .../mailreader2/MailreaderSupport_ja.properties |   89 -
 .../mailreader2/MailreaderSupport_ru.properties |   89 -
 ...egistration-Registration_save-validation.xml |   28 -
 .../mailreader2/Registration-validation.xml     |   32 -
 src/main/java/mailreader2/Registration.java     |  122 -
 ...ubscription-Subscription_save-validation.xml |   23 -
 .../mailreader2/Subscription-validation.xml     |   11 -
 src/main/java/mailreader2/Subscription.java     |  145 -
 src/main/java/mailreader2/Welcome.java          |   49 -
 src/main/java/struts.xml                        |   16 -
 src/main/resources/LICENSE.txt                  |  174 --
 src/main/resources/NOTICE.txt                   |    5 -
 src/main/resources/log4j2.xml                   |   16 -
 src/main/resources/velocity.properties          |    1 -
 src/main/webapp/META-INF/context.xml            |    3 -
 src/main/webapp/WEB-INF/database.xml            |    9 -
 src/main/webapp/WEB-INF/jsp/ChangePassword.jsp  |   25 -
 src/main/webapp/WEB-INF/jsp/Error.jsp           |   40 -
 src/main/webapp/WEB-INF/jsp/Footer.jsp          |    6 -
 src/main/webapp/WEB-INF/jsp/Login.jsp           |   30 -
 src/main/webapp/WEB-INF/jsp/MainMenu.jsp        |   25 -
 src/main/webapp/WEB-INF/jsp/Registration.jsp    |  115 -
 src/main/webapp/WEB-INF/jsp/Subscription.jsp    |   60 -
 src/main/webapp/WEB-INF/jsp/Welcome.jsp         |   55 -
 src/main/webapp/WEB-INF/web.xml                 |   47 -
 src/main/webapp/css/mailreader.css              |   46 -
 src/main/webapp/index.html                      |   10 -
 src/main/webapp/struts-power.gif                |  Bin 1798 -> 0 bytes
 src/main/webapp/tour.html                       | 2470 ------------------
 86 files changed, 5164 insertions(+), 5164 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/struts-examples/blob/0677ec5c/README.txt
----------------------------------------------------------------------
diff --git a/README.txt b/README.txt
deleted file mode 100644
index 1ce884b..0000000
--- a/README.txt
+++ /dev/null
@@ -1,23 +0,0 @@
-README.txt - mailreader 
-
-The MailReader demonstrates a localized application with a master/child 
-CRUD workflow. 
-
-This rendition also demonstrates using wildcards to "normalize" an 
-application. 
-
-See the Sandbox for other MailReader examples using other architectures. 
-
-* http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2/apps/
-
-For more about the MailReader applicaton genneraly, visit Struts University.
-
-* http://www.StrutsUniversity.org/
-
-I18N:
-=====
-Please note that this project was created with the assumption that it will be run
-in an environment where the default locale is set to English. This means that
-the default messages defined in package.properties are in English. If the default
-locale for your server is different, then rename package.properties to package_en.properties
-and create a new package.properties with proper values for your default locale.

http://git-wip-us.apache.org/repos/asf/struts-examples/blob/0677ec5c/mailreader/README.txt
----------------------------------------------------------------------
diff --git a/mailreader/README.txt b/mailreader/README.txt
new file mode 100644
index 0000000..1ce884b
--- /dev/null
+++ b/mailreader/README.txt
@@ -0,0 +1,23 @@
+README.txt - mailreader 
+
+The MailReader demonstrates a localized application with a master/child 
+CRUD workflow. 
+
+This rendition also demonstrates using wildcards to "normalize" an 
+application. 
+
+See the Sandbox for other MailReader examples using other architectures. 
+
+* http://svn.apache.org/viewvc/struts/sandbox/trunk/struts2/apps/
+
+For more about the MailReader applicaton genneraly, visit Struts University.
+
+* http://www.StrutsUniversity.org/
+
+I18N:
+=====
+Please note that this project was created with the assumption that it will be run
+in an environment where the default locale is set to English. This means that
+the default messages defined in package.properties are in English. If the default
+locale for your server is different, then rename package.properties to package_en.properties
+and create a new package.properties with proper values for your default locale.

http://git-wip-us.apache.org/repos/asf/struts-examples/blob/0677ec5c/mailreader/pom.xml
----------------------------------------------------------------------
diff --git a/mailreader/pom.xml b/mailreader/pom.xml
new file mode 100644
index 0000000..0965639
--- /dev/null
+++ b/mailreader/pom.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+-->
+<project 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.apache.struts</groupId>
+      <artifactId>struts2-apps</artifactId>
+      <version>2.5-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>struts2-mailreader</artifactId>
+    <packaging>war</packaging>
+    <name>Struts 2 Mail Reader Webapp</name>
+
+    <dependencies>
+
+        <dependency>
+           <groupId>javax.servlet</groupId>
+           <artifactId>servlet-api</artifactId>
+           <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+           <groupId>${project.groupId}</groupId>
+           <artifactId>struts-mailreader-dao</artifactId>
+           <version>1.3.5</version>
+        </dependency>
+
+        <!-- Logging -->
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-core</artifactId>
+            <version>${log4j2.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.logging.log4j</groupId>
+            <artifactId>log4j-jcl</artifactId>
+            <version>${log4j2.version}</version>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <resources>
+            <resource>
+                <directory>src/main/java</directory>
+                <includes>
+                    <include>**/*.xml</include>
+                    <include>**/*.properties</include>
+                </includes>
+            </resource>
+        </resources>
+        <plugins>
+            <plugin>
+                <groupId>org.mortbay.jetty</groupId>
+                <artifactId>jetty-maven-plugin</artifactId>
+                <version>8.1.16.v20140903</version>
+                <configuration>
+                    <stopKey>CTRL+C</stopKey>
+                    <stopPort>8999</stopPort>
+                   <scanIntervalSeconds>10</scanIntervalSeconds>
+                    <webAppSourceDirectory>${basedir}/src/main/webapp/</webAppSourceDirectory>
+                    <webAppConfig>
+                        <contextPath>/struts2-mailreader</contextPath>
+                        <descriptor>${basedir}/src/main/webapp/WEB-INF/web.xml</descriptor>
+                    </webAppConfig>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+    <properties>
+    	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+</project>

http://git-wip-us.apache.org/repos/asf/struts-examples/blob/0677ec5c/mailreader/src/main/java/alternate.properties
----------------------------------------------------------------------
diff --git a/mailreader/src/main/java/alternate.properties b/mailreader/src/main/java/alternate.properties
new file mode 100644
index 0000000..03dbf27
--- /dev/null
+++ b/mailreader/src/main/java/alternate.properties
@@ -0,0 +1,3 @@
+password=Enter your Password here ==>
+struts.logo.path=struts-power.gif
+struts.logo.alt=Powered by Struts

http://git-wip-us.apache.org/repos/asf/struts-examples/blob/0677ec5c/mailreader/src/main/java/alternate_ja.properties
----------------------------------------------------------------------
diff --git a/mailreader/src/main/java/alternate_ja.properties b/mailreader/src/main/java/alternate_ja.properties
new file mode 100644
index 0000000..981adc8
--- /dev/null
+++ b/mailreader/src/main/java/alternate_ja.properties
@@ -0,0 +1 @@
+.password=\u30d1\u30b9\u30ef\u30fc\u30c9\u3092\u5165\u529b==>

http://git-wip-us.apache.org/repos/asf/struts-examples/blob/0677ec5c/mailreader/src/main/java/mailreader-default.xml
----------------------------------------------------------------------
diff --git a/mailreader/src/main/java/mailreader-default.xml b/mailreader/src/main/java/mailreader-default.xml
new file mode 100644
index 0000000..3ea53ed
--- /dev/null
+++ b/mailreader/src/main/java/mailreader-default.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE struts PUBLIC
+    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
+    "http://struts.apache.org/dtds/struts-2.0.dtd">
+
+<struts>
+
+    <package name="mailreader-default" namespace="/" extends="struts-default">
+
+        <interceptors>
+
+            <interceptor name="authentication"
+                         class="mailreader2.AuthenticationInterceptor"/>
+
+            <interceptor-stack name="user" >
+                <interceptor-ref name="authentication" />
+                <interceptor-ref name="defaultStack"/>
+            </interceptor-stack>
+
+            <interceptor-stack name="user-submit" >
+                <interceptor-ref name="tokenSession" />
+                <interceptor-ref name="user"/>
+            </interceptor-stack>
+
+            <interceptor-stack name="guest" >
+                <interceptor-ref name="defaultStack"/>
+            </interceptor-stack>
+
+        </interceptors>
+
+        <default-interceptor-ref name="user"/>
+
+        <global-results>
+            <result name="error">/pages/Error.jsp</result>
+            <result name="invalid.token">/pages/Error.jsp</result>
+            <result name="login" type="redirectAction">Login_input</result>
+        </global-results>
+
+        <global-exception-mappings>
+            <exception-mapping
+                    result="error"
+                    exception="java.lang.Throwable"/>
+        </global-exception-mappings>
+
+    </package>
+
+</struts>

http://git-wip-us.apache.org/repos/asf/struts-examples/blob/0677ec5c/mailreader/src/main/java/mailreader-support.xml
----------------------------------------------------------------------
diff --git a/mailreader/src/main/java/mailreader-support.xml b/mailreader/src/main/java/mailreader-support.xml
new file mode 100644
index 0000000..2438cb4
--- /dev/null
+++ b/mailreader/src/main/java/mailreader-support.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE struts PUBLIC
+    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
+    "http://struts.apache.org/dtds/struts-2.0.dtd">
+
+<struts>
+    <package name="mailreader-support" namespace="/" extends="mailreader-default">
+
+        <action name="Tour">
+            <result>/tour.html</result>
+            <interceptor-ref name="guest"/>
+        </action>
+
+        <action name="Welcome" class="mailreader2.Welcome">
+            <result>/WEB-INF/jsp/Welcome.jsp</result>
+            <interceptor-ref name="guest"/>
+        </action>
+
+        <action name="Logout" class="mailreader2.Logout">
+            <result type="redirectAction">Welcome</result>
+        </action>
+
+        <action name="Login_*"  method="{1}" class="mailreader2.Login">
+            <result name="input">/WEB-INF/jsp/Login.jsp</result>
+            <result name="cancel" type="redirectAction">Welcome</result>
+            <result type="redirectAction">MainMenu</result>
+            <result name="expired" type="chain">ChangePassword</result>
+            <exception-mapping
+                    exception="org.apache.struts.apps.mailreader.dao.ExpiredPasswordException"
+                    result="expired"/>
+            <interceptor-ref name="guest"/>
+        </action>
+
+        <action name="Registration_*" method="{1}" class="mailreader2.Registration">
+            <result name="input">/WEB-INF/jsp/Registration.jsp</result>
+            <result type="redirectAction">MainMenu</result>
+            <interceptor-ref name="guest"/>
+        </action>
+    </package>
+
+    <package name="subscription" namespace="/" extends="mailreader-support">
+
+        <global-results>
+            <result name="input">/WEB-INF/jsp/Subscription.jsp</result>
+            <result type="redirectAction">Registration_input</result>
+        </global-results>
+
+        <action name="Subscription_save" method="save" class="mailreader2.Subscription">
+            <interceptor-ref name="user-submit" />
+        </action>
+
+        <action name="Subscription_*" method="{1}" class="mailreader2.Subscription" />
+
+    </package>
+
+    <package name="wildcard" namespace="/" extends="mailreader-support">
+
+        <action name="*" class="mailreader2.MailreaderSupport">
+            <result>/WEB-INF/jsp/{1}.jsp</result>
+        </action>
+        
+    </package>
+</struts>

http://git-wip-us.apache.org/repos/asf/struts-examples/blob/0677ec5c/mailreader/src/main/java/mailreader2/ApplicationListener.java
----------------------------------------------------------------------
diff --git a/mailreader/src/main/java/mailreader2/ApplicationListener.java b/mailreader/src/main/java/mailreader2/ApplicationListener.java
new file mode 100644
index 0000000..35e39fd
--- /dev/null
+++ b/mailreader/src/main/java/mailreader2/ApplicationListener.java
@@ -0,0 +1,234 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package mailreader2;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts.apps.mailreader.dao.impl.memory.MemoryUserDatabase;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import java.io.*;
+
+/**
+ * <p><code>ServletContextListener</code> that initializes and finalizes the
+ * persistent storage of User and Subscription information for the Struts
+ * Demonstration Application, using an in-memory database backed by an XML
+ * file.</p>
+ * <p/>
+ * <p><strong>IMPLEMENTATION WARNING</strong> - If this web application is run
+ * from a WAR file, or in another environment where reading and writing of the
+ * web application resource is impossible, the initial contents will be copied
+ * to a file in the web application temporary directory provided by the
+ * container.  This is for demonstration purposes only - you should
+ * <strong>NOT</strong> assume that files written here will survive a restart
+ * of your servlet container.</p>
+ * <p/>
+ * <p>This class was borrowed from the Shale Mailreader. Changes were:</p>
+ * <p/>
+ * <ul>
+ * <p/>
+ * <li>Path to database.xml (under classes here). </li>
+ * <p/>
+ * <li>Class to store protocol list (an array here). </li>
+ * <p/>
+ * </ul>
+ * <p>
+ * DEVELOPMENT NOTE - Another approach would be to instantiate the database via Spring.
+ * </p>
+ */
+
+public final class ApplicationListener implements ServletContextListener {
+
+    // ------------------------------------------------------ Manifest Constants
+
+
+    /**
+     * <p>Appication scope attribute key under which the in-memory version of
+     * our database is stored.</p>
+     */
+    public static final String DATABASE_KEY = "database";
+
+
+    /**
+     * <p>Application scope attribute key under which the valid selection
+     * items for the protocol property is stored.</p>
+     */
+    public static final String PROTOCOLS_KEY = "protocols";
+
+    // ------------------------------------------------------ Instance Variables
+
+
+    /**
+     * <p>The <code>ServletContext</code> for this web application.</p>
+     */
+    private ServletContext context = null;
+
+
+    /**
+     * The {@link MemoryUserDatabase} object we construct and make available.
+     */
+    private MemoryUserDatabase database = null;
+
+
+    /**
+     * <p>Logging output for this plug in instance.</p>
+     */
+    private Logger log = LogManager.getLogger(this.getClass());
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * <p>The web application resource path of our persistent database storage
+     * file.</p>
+     */
+    private String pathname = "/WEB-INF/database.xml";
+
+    /**
+     * <p>Return the application resource path to the database.</p>
+     *
+     * @return application resource path path to the database
+     */
+    public String getPathname() {
+        return (this.pathname);
+    }
+
+    /**
+     * <p>Set the application resource path to the database.</p>
+     *
+     * @param pathname to the database
+     */
+    public void setPathname(String pathname) {
+        this.pathname = pathname;
+    }
+
+    // ------------------------------------------ ServletContextListener Methods
+
+
+    /**
+     * <p>Gracefully shut down this database, releasing any resources that
+     * were allocated at initialization.</p>
+     *
+     * @param event ServletContextEvent to process
+     */
+    public void contextDestroyed(ServletContextEvent event) {
+
+        log.info("Finalizing memory database plug in");
+
+        if (database != null) {
+            try {
+                database.close();
+            } catch (Exception e) {
+                log.error("Closing memory database", e);
+            }
+        }
+
+        context.removeAttribute(DATABASE_KEY);
+        context.removeAttribute(PROTOCOLS_KEY);
+        database = null;
+        context = null;
+
+    }
+
+
+    /**
+     * <p>Initialize and load our initial database from persistent
+     * storage.</p>
+     *
+     * @param event The context initialization event
+     */
+    public void contextInitialized(ServletContextEvent event) {
+
+        log.info("Initializing memory database plug in from '" +
+                pathname + "'");
+
+        // Remember our associated ServletContext
+        this.context = event.getServletContext();
+
+        // Construct a new database and make it available
+        database = new MemoryUserDatabase();
+        try {
+            String path = calculatePath();
+            if (log.isDebugEnabled()) {
+                log.debug(" Loading database from '" + path + "'");
+            }
+            database.setPathname(path);
+            database.open();
+        } catch (Exception e) {
+            log.error("Opening memory database", e);
+            throw new IllegalStateException("Cannot load database from '" +
+                    pathname + "': " + e);
+        }
+        context.setAttribute(DATABASE_KEY, database);
+
+    }
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * <p>Calculate and return an absolute pathname to the XML file to contain
+     * our persistent storage information.</p>
+     *
+     * @throws Exception if an input/output error occurs
+     */
+    private String calculatePath() throws Exception {
+
+        // Can we access the database via file I/O?
+        String path = context.getRealPath(pathname);
+        if (path != null) {
+            return (path);
+        }
+
+        // Does a copy of this file already exist in our temporary directory
+        File dir = (File)
+                context.getAttribute("javax.servlet.context.tempdir");
+        File file = new File(dir, "struts-example-database.xml");
+        if (file.exists()) {
+            return (file.getAbsolutePath());
+        }
+
+        // Copy the static resource to a temporary file and return its path
+        InputStream is =
+                context.getResourceAsStream(pathname);
+        BufferedInputStream bis = new BufferedInputStream(is, 1024);
+        FileOutputStream os =
+                new FileOutputStream(file);
+        BufferedOutputStream bos = new BufferedOutputStream(os, 1024);
+        byte buffer[] = new byte[1024];
+        while (true) {
+            int n = bis.read(buffer);
+            if (n <= 0) {
+                break;
+            }
+            bos.write(buffer, 0, n);
+        }
+        bos.close();
+        bis.close();
+        return (file.getAbsolutePath());
+
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/struts-examples/blob/0677ec5c/mailreader/src/main/java/mailreader2/AuthenticationInterceptor.java
----------------------------------------------------------------------
diff --git a/mailreader/src/main/java/mailreader2/AuthenticationInterceptor.java b/mailreader/src/main/java/mailreader2/AuthenticationInterceptor.java
new file mode 100644
index 0000000..77470a5
--- /dev/null
+++ b/mailreader/src/main/java/mailreader2/AuthenticationInterceptor.java
@@ -0,0 +1,52 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package mailreader2;
+
+import com.opensymphony.xwork2.Action;
+import com.opensymphony.xwork2.ActionInvocation;
+import com.opensymphony.xwork2.interceptor.Interceptor;
+import org.apache.struts.apps.mailreader.dao.User;
+
+import java.util.Map;
+
+public class AuthenticationInterceptor implements Interceptor  {
+
+    public void destroy () {}
+
+    public void init() {}
+
+    public String intercept(ActionInvocation actionInvocation) throws Exception {
+
+        Map session = actionInvocation.getInvocationContext().getSession();
+
+        User user = (User) session.get(Constants.USER_KEY);
+
+        boolean isAuthenticated = (null!=user) && (null!=user.getDatabase());
+
+        if (!isAuthenticated) {
+            return Action.LOGIN;            
+        }
+        else {
+            return actionInvocation.invoke();
+        }
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/struts-examples/blob/0677ec5c/mailreader/src/main/java/mailreader2/Constants.java
----------------------------------------------------------------------
diff --git a/mailreader/src/main/java/mailreader2/Constants.java b/mailreader/src/main/java/mailreader2/Constants.java
new file mode 100644
index 0000000..f33b771
--- /dev/null
+++ b/mailreader/src/main/java/mailreader2/Constants.java
@@ -0,0 +1,128 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package mailreader2;
+
+/**
+ * <p> Manifest constants for the MailReader application. </p>
+ */
+public final class Constants {
+
+    // --- Tokens ----
+
+    /**
+     * <p> The token representing a "cancel" request. </p>
+     */
+    public static final String CANCEL = "cancel";
+
+    /**
+     * <p> The token representing a "create" task. </p>
+     */
+    public static final String CREATE = "Create";
+
+    /**
+     * <p> The application scope attribute under which our user database is
+     * stored. </p>
+     */
+    public static final String DATABASE_KEY = "database";
+
+    /**
+     * <p> The token representing a "edit" task. </p>
+     */
+    public static final String DELETE = "Delete";
+
+    /**
+     * <p> The token representing a "edit" task. </p>
+     */
+    public static final String EDIT = "Edit";
+
+    /**
+     * <p> The package name for this application. </p>
+     */
+    public static final String PACKAGE = "org.apache.struts.apps.mailreader";
+
+    /**
+     * <p> The session scope attribute under which the Subscription object
+     * currently selected by our logged-in User is stored. </p>
+     */
+    public static final String SUBSCRIPTION_KEY = "subscription";
+
+    /**
+     * <p> The session scope attribute under which the User object for the
+     * currently logged in user is stored. </p>
+     */
+    public static final String USER_KEY = "user";
+
+    /**
+     * <p>The token representing the "Host" property.
+     */
+    public static final String HOST = "host";
+
+
+    // ---- Error Messages ----
+
+    /**
+     * <p>
+     * A static message in case message resource is not loaded.
+     * </p>
+     */
+    public static final String ERROR_MESSAGES_NOT_LOADED =
+            "ERROR:  Message resources not loaded -- check servlet container logs for error messages.";
+
+    /**
+     * <p>
+     * A static message in case database resource is not loaded.
+     * <p>
+     */
+    public static final String ERROR_DATABASE_NOT_LOADED =
+            "ERROR:  User database not loaded -- check servlet container logs for error messages.";
+
+    /**
+     * <p>
+     * A standard key from the message resources file, to test if it is available.
+     * <p>
+     */
+    public static final String ERROR_DATABASE_MISSING = "error.database.missing";
+
+    /**
+     * <P>
+     * A "magic" username to trigger an ExpiredPasswordException for testing.
+     *</p>
+     */
+    public static final String EXPIRED_PASSWORD_EXCEPTION = "ExpiredPasswordException";
+
+    /**
+     * <p>
+     * Name of field to associate with authentification errors.
+     * <p>
+     */
+    public static final String PASSWORD_MISMATCH_FIELD = "password";
+
+    // ---- Log Messages ----
+
+    /**
+     * <p> Message to log if saving a user fails. </p>
+     */
+    public static final String LOG_DATABASE_SAVE_ERROR =
+            " Unexpected error when saving User: ";
+
+
+}

http://git-wip-us.apache.org/repos/asf/struts-examples/blob/0677ec5c/mailreader/src/main/java/mailreader2/Login-validation.xml
----------------------------------------------------------------------
diff --git a/mailreader/src/main/java/mailreader2/Login-validation.xml b/mailreader/src/main/java/mailreader2/Login-validation.xml
new file mode 100644
index 0000000..1a65930
--- /dev/null
+++ b/mailreader/src/main/java/mailreader2/Login-validation.xml
@@ -0,0 +1,14 @@
+<!DOCTYPE validators PUBLIC "-//Apache Struts//XWork Validator 1.0.2//EN" "http://struts.apache.org/dtds/xwork-validator-1.0.2.dtd">
+
+<validators>
+    <field name="username">
+        <field-validator type="requiredstring">
+            <message key="error.username.required"/>
+        </field-validator>
+    </field>
+    <field name="password">
+        <field-validator type="requiredstring">
+            <message key="error.password.required"/>
+        </field-validator>
+    </field>
+</validators>

http://git-wip-us.apache.org/repos/asf/struts-examples/blob/0677ec5c/mailreader/src/main/java/mailreader2/Login.java
----------------------------------------------------------------------
diff --git a/mailreader/src/main/java/mailreader2/Login.java b/mailreader/src/main/java/mailreader2/Login.java
new file mode 100644
index 0000000..d4c1403
--- /dev/null
+++ b/mailreader/src/main/java/mailreader2/Login.java
@@ -0,0 +1,48 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package mailreader2;
+
+import org.apache.struts.apps.mailreader.dao.ExpiredPasswordException;
+import org.apache.struts.apps.mailreader.dao.User;
+
+/**
+ * <p> Validate a user login. </p>
+ */
+public final class Login extends MailreaderSupport {
+
+    public String execute() throws ExpiredPasswordException  {
+
+        User user = findUser(getUsername(), getPassword());
+
+        if (user != null) {
+            setUser(user);
+        }
+
+        if (hasErrors()) {
+            return INPUT;
+        }
+
+       return SUCCESS;
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts-examples/blob/0677ec5c/mailreader/src/main/java/mailreader2/Logout.java
----------------------------------------------------------------------
diff --git a/mailreader/src/main/java/mailreader2/Logout.java b/mailreader/src/main/java/mailreader2/Logout.java
new file mode 100644
index 0000000..2073296
--- /dev/null
+++ b/mailreader/src/main/java/mailreader2/Logout.java
@@ -0,0 +1,35 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package mailreader2;
+
+/**
+ * <p> Log user out of the current session. </p>
+ */
+public class Logout extends MailreaderSupport {
+
+    public String execute() {
+
+        setUser(null);
+
+        return SUCCESS;
+    }
+}

http://git-wip-us.apache.org/repos/asf/struts-examples/blob/0677ec5c/mailreader/src/main/java/mailreader2/MailreaderSupport.java
----------------------------------------------------------------------
diff --git a/mailreader/src/main/java/mailreader2/MailreaderSupport.java b/mailreader/src/main/java/mailreader2/MailreaderSupport.java
new file mode 100644
index 0000000..0136284
--- /dev/null
+++ b/mailreader/src/main/java/mailreader2/MailreaderSupport.java
@@ -0,0 +1,583 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package mailreader2;
+
+import com.opensymphony.xwork2.ActionSupport;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.struts.apps.mailreader.dao.ExpiredPasswordException;
+import org.apache.struts.apps.mailreader.dao.Subscription;
+import org.apache.struts.apps.mailreader.dao.User;
+import org.apache.struts.apps.mailreader.dao.UserDatabase;
+import org.apache.struts.apps.mailreader.dao.impl.memory.MemorySubscription;
+import org.apache.struts.apps.mailreader.dao.impl.memory.MemoryUser;
+import org.apache.struts2.interceptor.ApplicationAware;
+import org.apache.struts2.interceptor.SessionAware;
+
+import java.util.Map;
+
+/**
+ * <p> Base Action for MailreaderSupport application. </p>
+ * <p/>
+ * <p> Note that this class does NOT implement model driven because of the way
+ * the pre-existing model is designed. The MailReader DAO includes immutable
+ * fields that can only be set on construction, and some objects do not have a
+ * default construction. One approach would be to mirror all the DAO
+ * properties on the Actions. As an alternative, this implementations uses the
+ * DAO properties where possible, and uses local Action properties only as
+ * needed. To create new objects, a blank temporary object is constructed, and
+ * the page uses a mix of local Action properties and DAO properties. When the
+ * new object is to be saved, the local Action properties are used to create
+ * the object using the DAO factory methods, the input values are copied from
+ * the temporary object, and the new object is saved. It's kludge, but it
+ * avoids creating unnecessary local properties. Pick your poison.</p>
+ */
+public class MailreaderSupport extends ActionSupport
+        implements SessionAware, ApplicationAware {
+
+    /**
+     * Return CANCEL so apropriate result can be selected.
+     * @return "cancel" so apropriate result can be selected.
+     */
+    public String cancel() {
+        return Constants.CANCEL;
+    }
+
+    /**
+     * Convenience method to copy User properties.
+     **/
+    protected void copyUser(User source, User target) {
+      if ((source==null) || (target==null)) return;
+      target.setFromAddress(source.getFromAddress());
+      target.setFullName(source.getFullName());
+      target.setPassword(source.getPassword());
+      target.setReplyToAddress(source.getReplyToAddress());
+    }
+
+    /**
+     * Convenience method to copy Subscription properties.
+     **/
+    protected void copySubscription(Subscription source, Subscription target) {
+      if ((source==null) || (target==null)) return;
+      target.setAutoConnect(source.getAutoConnect());
+      target.setPassword(source.getPassword());
+      target.setType(source.getType());
+      target.setUsername(source.getUsername());
+    }
+
+
+    // ---- ApplicationAware ----
+
+    /**
+     * <p>Field to store application context or its proxy.</p>
+     * <p/>
+     * <p>The application context lasts for the life of the application. A
+     * reference to the database is stored in the application context at
+     * startup.</p>
+     */
+    private Map application;
+
+    /**
+     * <p>Store a new application context.</p>
+     *
+     * @param value A Map representing application state
+     */
+    public void setApplication(Map value) {
+        application = value;
+    }
+
+    /**
+     * <p>Provide application context.</p>
+     */
+    public Map getApplication() {
+        return application;
+    }
+
+    // ---- SessionAware ----
+
+    /**
+     * <p>Field to store session context, or its proxy.</p>
+     */
+    private Map session;
+
+    /**
+     * <p>Store a new session context.</p>
+     *
+     * @param value A Map representing session state
+     */
+    public void setSession(Map value) {
+        session = value;
+    }
+
+    /**
+     * <p>Provide session context.</p>
+     *
+     * @return session context
+     */
+    public Map getSession() {
+        return session;
+    }
+
+    // ---- Task property (utilized by UI) ----
+
+    /**
+     * <p>Field to store workflow task.</p>
+     * <p/>
+     * <p>The Task is used to track the state of the CRUD workflows. It can be
+     * set to Constant.CREATE, Constant.EDIT, or Constant.DELETE as
+     * needed.</p>
+     */
+    private String task = null;
+
+
+    /**
+     * <p>Provide worklow task.</p>
+     *
+     * @return Returns the task.
+     */
+    public String getTask() {
+        return task;
+    }
+
+    /**
+     * <p>Store new workflow task.</p>
+     *
+     * @param value The task to set.
+     */
+    public void setTask(String value) {
+        task =  value;
+    }
+
+    // ---- Token property (utilized by UI) ----
+
+    /**
+     * <p>Field to store double-submit guard.</p>
+     */
+    private String token = null;
+
+
+    /**
+     * <p>Provide Token.</p>
+     *
+     * @return Returns the token.
+     */
+    public String getToken() {
+        return token;
+    }
+
+    /**
+     * <p>Store new Token.</p>
+     *
+     * @param value The token to set.
+     */
+    public void setToken(String value) {
+        token =  value;
+    }
+
+
+    // ---- Host property ----
+
+    /**
+     * <p>Field to store Subscription host.</p>
+     * <p/>
+     * <p> The host is an immutable property of the Subscrtion DAP object, so
+     * we need to store it locally until we are ready to create the
+     * Subscription. </p>
+     */
+    private String host;
+
+    /**
+     * <p>Provide tSubscription host.</p>
+     *
+     * @return host property
+     */
+    public String getHost() {
+        return host;
+    }
+
+    /**
+     * <p>Store new Subscription host.</p>
+     *
+     * @param value
+     */
+    public void setHost(String value) {
+        host = value;
+    }
+
+    // ---- Password property ----
+
+    /**
+     * <p>Field to store User password property.</p>
+     * <p/>
+     * <p>The User DAO object password proerty is immutable, so we store it
+     * locally until we are ready to create the object.</p>
+     */
+    private String password = null;
+
+
+    /**
+     * <p>Provide User password</p>
+     *
+     * @return Returns the password.
+     */
+    public String getPassword() {
+        return password;
+    }
+
+    /**
+     * <p>Store new User Password</p>
+     *
+     * @param value The password to set.
+     */
+    public void setPassword(String value) {
+        password = value;
+    }
+
+    // ---- Password2 property (confirmation) ----
+
+    /**
+     * <p>Field to store the User password confirmation.</p>
+     * <p/>
+     * <p>When a User object is created, we ask the client to enter the
+     * password twice, to help ensure the password is being typed
+     * correctly.</p>
+     */
+    private String password2 = null;
+
+
+    /**
+     * <p>Provide the User password confirmation.</p>
+     *
+     * @return Returns the confirmationpassword.
+     */
+    public String getPassword2() {
+        return password2;
+    }
+
+    /**
+     * <p>Store a new User password confirmation.</p>
+     *
+     * @param value The confirmation password to set.
+     */
+    public void setPassword2(String value) {
+        password2 = value;
+    }
+
+    // ---- Username property ----
+
+    /**
+     * <p>Field to store User username.</p>
+     * <p/>
+     * <p>The User DAO object password proerty is immutable, so we store it
+     * locally until we are ready to create the object.</p>
+     */
+    private String username = null;
+
+
+    /**
+     * <p>Provide User username.</p>
+     *
+     * @return Returns the User username.
+     */
+    public String getUsername() {
+        return username;
+    }
+
+    /**
+     * <p>Store new User username</p>
+     *
+     * @param value The username to set.
+     */
+    public void setUsername(String value) {
+        username = value;
+    }
+
+    // ---- Database property ----
+
+    /**
+     * <p>Provide reference to UserDatabase, or null if the database is not
+     * available. </p>
+     *
+     * @return a reference to the UserDatabase or null if the database is not
+     *         available
+     */
+    public UserDatabase getDatabase() {
+        Object db = getApplication().get(Constants.DATABASE_KEY);
+        if (db == null) {
+            this.addActionError(getText("error.database.missing"));
+        }
+        return (UserDatabase) db;
+    }
+
+    /**
+     * <p>Store a new reference to UserDatabase</p>
+     *
+     * @param database
+     */
+    public void setDatabase(UserDatabase database) {
+        getApplication().put(Constants.DATABASE_KEY, database);
+    }
+
+    // ---- User property ----
+
+    /**
+     * <p>Provide reference to User object for authenticated user.</p>
+     *
+     * @return User object for authenticated user.
+     */
+    public User getUser() {
+        return (User) getSession().get(Constants.USER_KEY);
+    }
+
+    /**
+     * <p>Store new reference to User Object.</p>
+     *
+     * @param user User object for authenticated user
+     */
+    public void setUser(User user) {
+        getSession().put(Constants.USER_KEY, user);
+    }
+
+    /**
+     * <p>Obtain User object from database, or return null if the credentials
+     * are not found or invalid.</p>
+     *
+     * @param username User username
+     * @param password User password
+     * @return User object or null if not found
+     * @throws ExpiredPasswordException
+     */
+    public User findUser(String username, String password)
+            throws ExpiredPasswordException {
+        // FIXME: Stupid testing hack to compensate for inadequate DAO layer
+        if (Constants.EXPIRED_PASSWORD_EXCEPTION.equals(username)) {
+            throw new ExpiredPasswordException(Constants.EXPIRED_PASSWORD_EXCEPTION);
+        }
+
+        User user = getDatabase().findUser(username);
+        if ((user != null) && !user.getPassword().equals(password)) {
+            user = null;
+        }
+        if (user == null) {
+            this.addFieldError(Constants.PASSWORD_MISMATCH_FIELD,
+                    getText("error.password.mismatch"));
+        }
+        return user;
+    }
+
+    /**
+     * <p><code>Log</code> instance for this application. </p>
+     */
+    protected Logger log = LogManager.getLogger(Constants.PACKAGE);
+
+    /**
+     * <p> Persist the User object, including subscriptions, to the database.
+     * </p>
+     *
+     * @throws java.lang.Exception on database error
+     */
+    public void saveUser() throws Exception {
+        try {
+            getDatabase().save();
+        } catch (Exception e) {
+            String message = Constants.LOG_DATABASE_SAVE_ERROR + getUser()
+                    .getUsername();
+            log.error(message, e);
+            throw new Exception(message, e);
+        }
+    }
+
+    public void createInputUser() {
+        User user = new MemoryUser(null, null);
+        setUser(user);
+    }
+
+    /**
+     * <p> Verify input for creating a new user, create the user, and process
+     * the login. </p>
+     *
+     * @return A new User and empty Errors if create succeeds, or null and
+     *         Errors if create fails
+     */
+    public User createUser(String username, String password) {
+
+        UserDatabase database = getDatabase();
+        User user;
+
+        try {
+            user = database.findUser(username);
+         }
+
+        catch (ExpiredPasswordException e) {
+            user = getUser(); // Just so that it is not null
+        }
+
+        if (user != null) {
+            this.addFieldError("username", "error.username.unique");
+            return null;
+        }
+
+        return database.createUser(username);
+    }
+
+    // Since user.username is immutable, we have to use some local properties
+
+    /**
+     * <p>Use the current User object to create a new User object, and make
+     * the new User object the authenticated user.</p>
+     * <p/>
+     * <p>The "current" User object is usually a temporary object being used
+     * to capture input.</p>
+     *
+     * @param _username User username
+     * @param _password User password
+     */
+    public void copyUser(String _username, String _password) {
+        User input = getUser();
+        input.setPassword(_password);
+        User user = createUser(_username, _password);
+        if (null != user) {
+            copyUser(input,user);
+            setUser(user);
+        }
+    }
+
+    // ---- Subscription property ----
+
+    /**
+     * <p>Obtain the cached Subscription object, if any. </p>
+     *
+     * @return Cached Subscription object or null
+     */
+    public Subscription getSubscription() {
+        return (Subscription) getSession().get(Constants.SUBSCRIPTION_KEY);
+    }
+
+    /**
+     * <p>Store new User Subscription.</p>
+     *
+     * @param subscription
+     */
+    public void setSubscription(Subscription subscription) {
+        getSession().put(Constants.SUBSCRIPTION_KEY, subscription);
+    }
+
+    /**
+     * <p> Obtain User Subscription object for the given host, or return null
+     * if not found. </p>
+     *
+     * <p>It would be possible for this code to throw a NullPointerException,
+     * but the ExceptionHandler in the xwork.xml will catch that for us.</p>
+     *
+     * @return The matching Subscription or null
+     */
+    public Subscription findSubscription(String host) {
+        Subscription subscription;
+        subscription = getUser().findSubscription(host);
+        return subscription;
+    }
+
+    /**
+     * <p>Obtain uSER Subscription for the local Host property.</p>
+     * <p/>
+     * <p>Usually, the host property will be set from the client request,
+     * because it was embedded in a link to the Subcription action.
+     *
+     * @return Subscription or null if not found
+     */
+    public Subscription findSubscription() {
+        return findSubscription(getHost());
+    }
+
+    /**
+     * <p>Provide a "temporary" User Subscription object that can be used to
+     * capture input values.</p>
+     */
+    public void createInputSubscription() {
+        Subscription sub = new MemorySubscription(getUser(), null);
+        setSubscription(sub);
+        setHost(sub.getHost());
+    }
+
+    /**
+     * <p>Provide new User Subscription object for the given host, or null if
+     * the host is not unique.</p>
+     *
+     * @param host
+     * @return New User Subscription object or null
+     */
+    public Subscription createSubscription(String host) {
+
+        Subscription sub;
+
+        sub = findSubscription(host);
+
+        if (null != sub) {
+            // FIXME - localization - "error.host.unique")
+            addFieldError(Constants.HOST,"That hostname is already defined");
+            return null;
+        }
+
+        return getUser().createSubscription(host);
+    }
+
+    /**
+     * <p>Create a new Subscription from the current Subscription object,
+     * making the new Subscription the current Subscription. </p>
+     * <p/>
+     * <p>Usually, the "current" Subscription is a temporary object being used
+     * to capture input values.</p>
+     *
+     * @param host
+     */
+    public void copySubscription(String host) {
+        Subscription input = getSubscription();
+        Subscription sub = createSubscription(host);
+        if (null != sub) {
+            copySubscription(input, sub);
+            setSubscription(sub);
+            setHost(sub.getHost());
+        }
+    }
+
+    /**
+     * <p>Delete the current Subscription object from the database.</p>
+     */
+    public void removeSubscription()  {
+        getUser().removeSubscription(getSubscription());
+        getSession().remove(Constants.SUBSCRIPTION_KEY);
+    }
+
+    /**
+     * <p>Provide MailServer Host for current User Subscription.</p>
+     *
+     * @return MailServer Host for current User Subscription
+     */
+    public String getSubscriptionHost() {
+        Subscription sub = getSubscription();
+        if (null == sub) {
+            return null;
+        }
+        return sub.getHost();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts-examples/blob/0677ec5c/mailreader/src/main/java/mailreader2/MailreaderSupport.properties
----------------------------------------------------------------------
diff --git a/mailreader/src/main/java/mailreader2/MailreaderSupport.properties b/mailreader/src/main/java/mailreader2/MailreaderSupport.properties
new file mode 100644
index 0000000..4e79b02
--- /dev/null
+++ b/mailreader/src/main/java/mailreader2/MailreaderSupport.properties
@@ -0,0 +1,97 @@
+button.cancel=Cancel
+button.confirm=Confirm
+button.doSubmit=DO_SUBMIT
+button.doReset=DO_RESULT
+button.doCancel=org.apache.struts.taglib.html.CANCEL
+button.reset=Reset
+button.save=Save
+button.logon=Log on
+change.message=Your password has expired.  Please ask the system administrator to change it.
+change.try=Try Again
+change.title=Password Has Expired
+database.load=Cannot load database from {0}
+error.database.missing=User database is missing, cannot validate login credentials
+error.fromAddress.format=Invalid format for From Address
+error.fromAddress.required=From Address is required
+error.fullName.required=Full Name is required
+error.host.required=Mail Server is required
+error.noSubscription=No Subscription bean in user session
+error.password.expired=Your password has expired for username {0}
+error.password.required=Password is required
+error.password2.required=Confirmation password is required
+error.password.match=Password and confirmation password must match
+error.password.mismatch=Invalid username and/or password, please try again
+error.replyToAddress.format=Invalid format for Reply To Address
+struts.messages.invalid.token=Cannot submit this form out of order
+error.type.invalid=Server Type must be 'imap' or 'pop3'
+error.type.required=Server Type is required
+error.username.required=Username is required
+error.username.unique=That username is already in use - please select another
+errors.footer=</ul><hr>
+errors.header=<h3><font color="red">Validation Error</font></h3><p>You must correct the following error(s) before proceeding:</p><ul>
+errors.prefix=<li>
+errors.suffix=</li>
+errors.ioException=I/O exception rendering error messages: {0}
+expired.password=User Password has expired for {0}
+heading.autoConnect=Auto
+heading.subscriptions=Current Subscriptions
+heading.host=Host Name
+heading.user=User Name
+heading.type=Server Type
+heading.action=Action
+index.heading=MailReader Demonstration Application Options
+index.login=Log on to the MailReader Demonstration Application
+index.registration=Register with the MailReader Demonstration Application
+index.title=MailReader Demonstration Application
+index.tour=A Walking Tour of the MailReader Demonstration Application
+linkSubscription.io=I/O Error: {0}
+linkSubscription.noSubscription=No subscription under attribute {0}
+linkUser.io=I/O Error: {0}
+linkUser.noUser=No user under attribute {0}
+login.title=MailReader Demonstration Application - Login
+mainMenu.heading=Main Menu Options for 
+mainMenu.logout=Log off MailReader Demonstration Application
+mainMenu.registration=Edit your user registration profile
+mainMenu.title=MailReader Demonstration Application - Main Menu
+option.imap=IMAP Protocol
+option.pop3=POP3 Protocol
+# prompt.
+host=Mail Server
+password=Password
+password2=(Repeat) Password
+username=Username
+
+registration.addSubscription=Add
+registration.deleteSubscription=Delete
+registration.editSubscription=Edit
+registration.title.create=Register for the MailReader Demonstration Application
+registration.title.edit=Edit Registration for the MailReader Demonstration Application
+
+subscription.autoConnect=Auto Connect
+subscription.password=Mail Password
+subscription.type=Server Type
+subscription.username=Mail Username
+subscription.title.create=Create New Mail Subscription
+subscription.title.delete=Delete Existing Mail Subscription
+subscription.title.edit=Edit Existing Mail Subscription
+
+user.fromAddress=From Address
+user.fullName=Full Name
+user.replyToAddress=Reply To Address
+
+# Standard error messages for validator framework checks
+errors.required=${getText(fieldName)} is required.
+errors.minlength=${getText(fieldName)} cannot be less than {1} characters.
+errors.maxlength=${getText(fieldName)} cannot be greater than {1} characters.
+errors.invalid=${getText(fieldName)} is invalid.
+errors.byte=${getText(fieldName)} must be an byte.
+errors.short=${getText(fieldName)} must be an short.
+errors.integer=${getText(fieldName)} must be an integer.
+errors.long=${getText(fieldName)} must be an long.
+errors.float=${getText(fieldName)} must be an float.
+errors.double=${getText(fieldName)} must be an double.
+errors.date=${getText(fieldName)} is not a date.
+errors.range=${getText(fieldName)} is not in the range ${minLength} through ${maxLength}.
+errors.creditcard=${getText(fieldName)} is not a valid credit card number.
+errors.email=${getText(fieldName)} is an invalid e-mail address.
+errors.literal=${getText(fieldName)}

http://git-wip-us.apache.org/repos/asf/struts-examples/blob/0677ec5c/mailreader/src/main/java/mailreader2/MailreaderSupport_ja.properties
----------------------------------------------------------------------
diff --git a/mailreader/src/main/java/mailreader2/MailreaderSupport_ja.properties b/mailreader/src/main/java/mailreader2/MailreaderSupport_ja.properties
new file mode 100644
index 0000000..2b9d2e4
--- /dev/null
+++ b/mailreader/src/main/java/mailreader2/MailreaderSupport_ja.properties
@@ -0,0 +1,89 @@
+button.cancel=\u30ad\u30e3\u30f3\u30bb\u30eb
+button.confirm=\u78ba\u8a8d
+button.reset=\u30ea\u30bb\u30c3\u30c8
+button.save=\u4fdd\u5b58
+change.message=\u30D1\u30B9\u30EF\u30FC\u30C9\u306E\u6709\u52B9\u671F\u9650\u304C\u904E\u304E\u307E\u3057\u305F\u3002\u30B7\u30B9\u30C6\u30E0\u7BA1\u7406\u8005\u306B\u304A\u554F\u3044\u5408\u308F\u305B\u4E0B\u3055\u3044
+change.try=\u518D\u8A66\u884C
+change.title=\u30d1\u30b9\u30ef\u30fc\u30c9\u671f\u9650\u5207\u308c
+database.load= {0} \u304B\u3089\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u3092\u30ED\u30FC\u30C9\u3067\u304D\u307E\u305B\u3093
+error.database.missing=\u30E6\u30FC\u30B6\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002\u30ED\u30B0\u30AA\u30F3\u306E\u8A8D\u8A3C\u304C\u51FA\u6765\u307E\u305B\u3093
+error.fromAddress.format=From\u30A2\u30C9\u30EC\u30B9\u306E\u66F8\u5F0F\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093
+error.fromAddress.required=From\u30A2\u30C9\u30EC\u30B9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044
+error.fullName.required=\u30D5\u30EB\u30CD\u30FC\u30E0\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044
+error.host.required=\u30E1\u30FC\u30EB\u30B5\u30FC\u30D0\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044
+error.noSubscription=Subscription bean \u304c\u30bb\u30c3\u30b7\u30e7\u30f3\u306e\u4e2d\u306b\u3042\u308a\u307e\u305b\u3093
+error.password.expired=\u30E6\u30FC\u30B6 {0} \u306E\u30D1\u30B9\u30EF\u30FC\u30C9\u306E\u6709\u52B9\u671F\u9650\u304C\u904E\u304E\u307E\u3057\u305F
+error.password.required=\u30D1\u30B9\u30EF\u30FC\u30C9\u304C\u5FC5\u8981\u3067\u3059
+error.password2.required=\u30D1\u30B9\u30EF\u30FC\u30C9(\u78BA\u8A8D\u7528)\u304C\u5FC5\u8981\u3067\u3059
+error.password.match=\u30D1\u30B9\u30EF\u30FC\u30C9\u3068\u78BA\u8A8D\u7528\u30D1\u30B9\u30EF\u30FC\u30C9\u304C\u4E00\u81F4\u3057\u3066\u3044\u307E\u305B\u3093
+error.password.mismatch=\u30E6\u30FC\u30B6\u540D\u307E\u305F\u306F\u30D1\u30B9\u30EF\u30FC\u30C9\u304C\u4E0D\u6B63\u3067\u3059\u3002\u518D\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044
+error.replyToAddress.format=\u8FD4\u4FE1\u30A2\u30C9\u30EC\u30B9\u306E\u66F8\u5F0F\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093
+struts.messages.invalid.token=\u3053\u306E\u30D5\u30A9\u30FC\u30E0\u306E\u5185\u5BB9\u304C\u6B63\u3057\u304F\u306A\u3044\u305F\u3081\u9001\u4FE1\u3059\u308B\u3053\u3068\u304C\u51FA\u6765\u307E\u305B\u3093
+error.type.invalid=\u30B5\u30FC\u30D0\u30BF\u30A4\u30D7\u306F 'imap' \u304B 'pop3'\u306E\u3069\u3061\u3089\u304B\u3067\u306A\u3051\u308C\u3070\u306A\u308A\u307E\u305B\u3093
+error.type.required=\u30B5\u30FC\u30D0\u30BF\u30A4\u30D7\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044
+error.username.required=\u30E6\u30FC\u30B6\u540D\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044
+error.username.unique=\u305D\u306E\u30E6\u30FC\u30B6\u540D\u306F\u65E2\u306B\u4F7F\u7528\u3055\u308C\u3066\u3044\u307E\u3059\u3002 \u5225\u306E\u30E6\u30FC\u30B6\u540D\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044
+errors.footer=</ul><hr>
+errors.header=<h3><font color="red">\u5165\u529b\u30c1\u30a7\u30c3\u30af\u30a8\u30e9\u30fc</font></h3><p>\u4ee5\u4e0b\u306e\u30a8\u30e9\u30fc\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044:</p><ul>
+errors.prefix=<li>
+errors.suffix=</li>
+errors.ioException=I/O\u4f8b\u5916\u304c\u767a\u751f\u3057\u307e\u3057\u305f: {0}
+expired.password=\u30E6\u30FC\u30B6 {0} \u306E\u30D1\u30B9\u30EF\u30FC\u30C9\u306E\u6709\u52B9\u671F\u9650\u304C\u904E\u304E\u307E\u3057\u305F
+heading.autoConnect=\u81ea\u52d5\u63a5\u7d9a
+heading.subscriptions=\u73fe\u5728\u306e\u8cfc\u8aad\u60c5\u5831
+heading.host=\u30db\u30b9\u30c8\u540d
+heading.user=\u30e6\u30fc\u30b6\u540d
+heading.type=\u30b5\u30fc\u30d0\u30bf\u30a4\u30d7
+heading.action=\u64cd\u4f5c
+index.heading=MailReader\u30c7\u30e2\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3 \u30aa\u30d7\u30b7\u30e7\u30f3
+index.login=MailReader\u30c7\u30e2\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3 - \u30ed\u30b0\u30aa\u30f3
+index.registration=MailReader\u30c7\u30e2\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3 - \u30e6\u30fc\u30b6\u767b\u9332
+index.title=MailReader\u30c7\u30e2\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3(Struts 1.1-dev)
+index.tour=\u30b5\u30f3\u30d7\u30eb\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u6563\u7b56\u3059\u308b
+linkSubscription.io=I/O\u30a8\u30e9\u30fc: {0}
+linkSubscription.noSubscription=\u5c5e\u6027 {0} \u306b\u8cfc\u8aad\u60c5\u5831\u304c\u5b58\u5728\u3057\u307e\u305b\u3093  
+linkUser.io=I/O\u30a8\u30e9\u30fc: {0}
+linkUser.noUser=\u5c5e\u6027 {0} \u306b\u30e6\u30fc\u30b6\u60c5\u5831\u304c\u5b58\u5728\u3057\u307e\u305b\u3093
+login.title=MailReader\u30c7\u30e2\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3 - \u30ed\u30b0\u30aa\u30f3
+mainMenu.heading=\u30e1\u30a4\u30f3\u30e1\u30cb\u30e5\u30fc
+mainMenu.logout=MailReader \u30c7\u30e2\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u30ed\u30b0\u30aa\u30d5
+mainMenu.registration=\u30d7\u30ed\u30d5\u30a1\u30a4\u30eb\u306e\u7de8\u96c6
+mainMenu.title=MailReader\u30c7\u30e2\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3 - \u30e1\u30a4\u30f3\u30e1\u30cb\u30e5\u30fc
+option.imap=IMAP \u30d7\u30ed\u30c8\u30b3\u30eb
+option.pop3=POP3 \u30d7\u30ed\u30c8\u30b3\u30eb
+# prompt.
+autoConnect=\u81ea\u52d5\u63a5\u7d9a
+fromAddress=From\u30a2\u30c9\u30ec\u30b9
+fullName=\u30d5\u30eb\u30cd\u30fc\u30e0
+mailHostname=\u30e1\u30fc\u30eb\u30b5\u30fc\u30d0
+mailPassword=\u30e1\u30fc\u30eb\u30d1\u30b9\u30ef\u30fc\u30c9
+mailServerType=\u30b5\u30fc\u30d0\u30bf\u30a4\u30d7
+mailUsername=\u30e1\u30fc\u30eb\u30e6\u30fc\u30b6\u540d
+.password=\u30d1\u30b9\u30ef\u30fc\u30c9
+password2=\u30d1\u30b9\u30ef\u30fc\u30c9(\u78ba\u8a8d\u7528)
+replyToAddress=\u8fd4\u4fe1\u30a2\u30c9\u30ec\u30b9
+username=\u30e6\u30fc\u30b6\u540d
+registration.addSubscription=\u65b0\u898f\u4f5c\u6210
+registration.deleteSubscription=\u524a\u9664
+registration.editSubscription=\u7de8\u96c6
+registration.title.create=MailReader\u30c7\u30e2\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3 - \u30e6\u30fc\u30b6\u767b\u9332
+registration.title.edit=MailReader\u30c7\u30e2\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3 - \u30d7\u30ed\u30d5\u30a1\u30a4\u30eb\u7de8\u96c6
+subscription.title.create=\u30e1\u30fc\u30eb\u8cfc\u8aad\u60c5\u5831\u306e\u65b0\u898f\u4f5c\u6210
+subscription.title.delete=\u30e1\u30fc\u30eb\u8cfc\u8aad\u60c5\u5831\u306e\u524a\u9664
+subscription.title.edit=\u30e1\u30fc\u30eb\u8cfc\u8aad\u60c5\u5831\u306e\u7de8\u96c6
+
+# Standard error messages for validator framework checks
+errors.required=${getText(fieldName)} \u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002
+errors.minlength=${getText(fieldName)} \u306f {1} \u6587\u5b57\u4ee5\u4e0a\u3067\u306a\u3051\u308c\u3070\u306a\u308a\u307e\u305b\u3093\u3002
+errors.maxlength=${getText(fieldName)} \u306f {2} \u6587\u5b57\u4ee5\u4e0b\u3067\u306a\u3051\u308c\u3070\u306a\u308a\u307e\u305b\u3093\u3002
+errors.invalid=${getText(fieldName)} \u306f\u6b63\u3057\u304f\u3042\u308a\u307e\u305b\u3093\u3002
+errors.byte=${getText(fieldName)} \u306fbyte\u578b\u3067\u306a\u3051\u308c\u3070\u306a\u308a\u307e\u305b\u3093\u3002
+errors.short=${getText(fieldName)} \u306fshort\u578b\u3067\u306a\u3051\u308c\u3070\u306a\u308a\u307e\u305b\u3093\u3002
+errors.integer=${getText(fieldName)} \u306finteger\u578b\u3067\u306a\u3051\u308c\u3070\u306a\u308a\u307e\u305b\u3093\u3002
+errors.long=${getText(fieldName)} \u306flong\u578b\u3067\u306a\u3051\u308c\u3070\u306a\u308a\u307e\u305b\u3093\u3002
+errors.float=${getText(fieldName)} \u306ffloat\u578b\u3067\u306a\u3051\u308c\u3070\u306a\u308a\u307e\u305b\u3093\u3002
+errors.double=${getText(fieldName)} \u306fdouble\u578b\u3067\u306a\u3051\u308c\u3070\u306a\u308a\u307e\u305b\u3093\u3002
+errors.date=${getText(fieldName)} \u306f\u65e5\u4ed8\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002
+errors.range=${getText(fieldName)} \u306f {1} \u304b\u3089 {2} \u306e\u9593\u3067\u306a\u3051\u308c\u3070\u306a\u308a\u307e\u305b\u3093\u3002
+errors.creditcard=${getText(fieldName)} \u306f\u6b63\u3057\u3044\u30af\u30ec\u30b8\u30c3\u30c8\u30ab\u30fc\u30c9\u756a\u53f7\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002
+errors.email=${getText(fieldName)} \u306f\u6b63\u3057\u3044\u30e1\u30fc\u30eb\u30a2\u30c9\u30ec\u30b9\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002

http://git-wip-us.apache.org/repos/asf/struts-examples/blob/0677ec5c/mailreader/src/main/java/mailreader2/MailreaderSupport_ru.properties
----------------------------------------------------------------------
diff --git a/mailreader/src/main/java/mailreader2/MailreaderSupport_ru.properties b/mailreader/src/main/java/mailreader2/MailreaderSupport_ru.properties
new file mode 100644
index 0000000..6c7a88b
--- /dev/null
+++ b/mailreader/src/main/java/mailreader2/MailreaderSupport_ru.properties
@@ -0,0 +1,89 @@
+button.cancel=\u041e\u0442\u043c\u0435\u043d\u0438\u0442\u044c
+button.confirm=\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c
+button.reset=\u0421\u0431\u0440\u043e\u0441\u0438\u0442\u044c
+button.save=\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c
+change.message=Your password has expired.  Please ask the system administrator to change it.
+change.try=Try Again
+change.title=Password Has Expired
+database.load=\u0411\u0430\u0437\u0430 \u0434\u0430\u043d\u043d\u044b\u0445 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0437\u0430\u0433\u0440\u0443\u0436\u0435\u043d\u0430 \u0438\u0437 {0}
+error.database.missing=\u041d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u043e \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f - \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043f\u0440\u043e\u0432\u0435\u0441\u0442\u0438 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e.
+error.fromAddress.format=\u0412 \u043f\u043e\u043b\u0435 '\u0410\u0434\u0440\u0435\u0441 \u041e\u0442:' \u0443\u043a\u0430\u0437\u0430\u043d \u0430\u0434\u0440\u0435\u0441 \u0432 \u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u0435.
+error.fromAddress.required=\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0430\u0434\u0440\u0435\u0441 \u0432 \u043f\u043e\u043b\u0435 '\u0410\u0434\u0440\u0435\u0441 \u041e\u0442:'.
+error.fullName.required=\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u043e\u043b\u043d\u043e\u0435 \u0438\u043c\u044f.
+error.host.required=\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u043e\u0447\u0442\u043e\u0432\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440.
+error.noSubscription=\u041f\u043e\u0434\u043f\u0438\u0441\u043a\u0430 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430 \u0432 \u0441\u0435\u0441\u0441\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f
+error.password.expired=Your password has expired for username {0}
+error.password.required=\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u0430\u0440\u043e\u043b\u044c.
+error.password2.required=\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u043f\u0430\u0440\u043e\u043b\u044f.
+error.password.match=\u041f\u0430\u0440\u043e\u043b\u044c \u0438 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u043f\u0430\u0440\u043e\u043b\u044f \u043d\u0435 \u0441\u043e\u0432\u043f\u0430\u0434\u0430\u044e\u0442.
+error.password.mismatch=\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0435 \u0438\u043c\u044f \u0438/\u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c - \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435, \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0441\u043d\u043e\u0432\u0430.
+error.replyToAddress.format=\u0412 \u043f\u043e\u043b\u0435 '\u0410\u0434\u0440\u0435\u0441 \u041e\u0442\u0432\u0435\u0442\u0438\u0442\u044c \u043d\u0430:' \u0443\u043a\u0430\u0437\u0430\u043d \u0430\u0434\u0440\u0435\u0441 \u0432 \u043d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u0435.
+struts.messages.invalid.token=\u042d\u0442\u0430 \u0444\u043e\u0440\u043c\u0430 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u043d\u0430 - \u043d\u0430\u0440\u0443\u0448\u0435\u043d\u0438\u0435 \u043f\u043e\u0440\u044f\u0434\u043a\u0430 \u0437\u0430\u043d\u0435\u0441\u0435\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445.
+error.type.invalid=\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0442\u0438\u043f\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0443\u043a\u0430\u0437\u0430\u043d\u044b \u043b\u0438\u0448\u044c 'imap' \u0438\u043b\u0438 'pop3'
+error.type.required=\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0442\u0438\u043f \u0441\u0435\u0440\u0432\u0435\u0440\u0430
+error.username.required=\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f
+error.username.unique=\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u0435 \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f - \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435, \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0434\u0440\u0443\u0433\u043e\u0435 \u0438\u043c\u044f.
+errors.footer=</ul><hr>
+errors.header=<h3><font color="red">\u041e\u0448\u0438\u0431\u043a\u0438 \u043f\u0440\u0438 \u0437\u0430\u043d\u0435\u0441\u0435\u043d\u0438\u0438 \u0434\u0430\u043d\u043d\u044b\u0445</font></h3><p>\u0412\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0438\u0441\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0435 \u043d\u0438\u0436\u0435 \u043e\u0448\u0438\u0431\u043a\u0438:</p><ul>
+errors.prefix=<li>
+errors.suffix=</li>
+errors.ioException=\u041e\u0448\u0438\u0431\u043a\u0430 \u0432\u0432\u043e\u0434\u0430/\u0432\u044b\u0432\u043e\u0434\u0430 \u043f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 \u043e\u0431 \u043e\u0448\u0438\u0431\u043a\u0430\u0445: {0}
+expired.password=User Password has expired for {0}
+heading.autoConnect=\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438
+heading.subscriptions=\u0417\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438
+heading.host=\u0421\u0435\u0440\u0432\u0435\u0440
+heading.user=\u0418\u043c\u044f
+heading.type=\u0422\u0438\u043f \u0441\u0435\u0440\u0432\u0435\u0440\u0430
+heading.action=\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u0435
+index.heading=\u0414\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 '\u0427\u0442\u0435\u043d\u0438\u0435 \u043f\u043e\u0447\u0442\u044b'
+index.login=\u0412\u043e\u0439\u0442\u0438 \u043a\u0430\u043a \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c
+index.registration=\u0417\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f
+index.title=\u0414\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 '\u0427\u0442\u0435\u043d\u0438\u0435 \u043f\u043e\u0447\u0442\u044b' (Struts 1.1-dev)
+index.tour=\u041e\u0431\u0437\u043e\u0440 \u0414\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f '\u0427\u0442\u0435\u043d\u0438\u0435 \u043f\u043e\u0447\u0442\u044b'
+linkSubscription.io=\u041e\u0448\u0438\u0431\u043a\u0430 \u0432\u0432\u043e\u0434\u0430/\u0432\u044b\u0432\u043e\u0434\u0430 (\u0434\u043b\u044f \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438): {0}
+linkSubscription.noSubscription=\u0410\u0442\u0440\u0438\u0431\u0443\u0442 {0} \u043d\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0435 \u0438\u043b\u0438 \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442.
+linkUser.io=\u041e\u0448\u0438\u0431\u043a\u0430 \u0432\u0432\u043e\u0434\u0430/\u0432\u044b\u0432\u043e\u0434\u0430 (\u0434\u043b\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f): {0}
+linkUser.noUser=\u0410\u0442\u0440\u0438\u0431\u0443\u0442 {0} \u043d\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435 \u0438\u043b\u0438 \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442.
+login.title=\u0414\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0427\u0442\u0435\u043d\u0438\u0435 \u043f\u043e\u0447\u0442\u044b - \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0438\u043c\u0435\u043d\u0438 \u0438 \u043f\u0430\u0440\u043e\u043b\u044f.
+mainMenu.heading=\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0433\u043b\u0430\u0432\u043d\u043e\u0433\u043e \u043c\u0435\u043d\u044e \u0434\u043b\u044f
+mainMenu.logout=\u0412\u044b\u0439\u0442\u0438
+mainMenu.registration=\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0441\u0432\u043e\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438
+mainMenu.title=\u0414\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 '\u0427\u0442\u0435\u043d\u0438\u0435 \u043f\u043e\u0447\u0442\u044b' - \u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0435 \u043c\u0435\u043d\u044e
+option.imap=\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b IMAP 
+option.pop3=\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b POP3 
+# prompt.
+autoConnect=\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435:
+fromAddress=\u0410\u0434\u0440\u0435\u0441 \u041e\u0442:
+fullName=\u041f\u043e\u043b\u043d\u043e\u0435 \u0438\u043c\u044f:
+mailHostname=\u041f\u043e\u0447\u0442\u043e\u0432\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440:
+mailPassword=\u041f\u0430\u0440\u043e\u043b\u044c \u0434\u043b\u044f \u043f\u043e\u0447\u0442\u043e\u0432\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430:
+mailServerType=\u0422\u0438\u043f \u0441\u0435\u0440\u0432\u0435\u0440\u0430:
+mailUsername=\u0418\u043c\u044f \u0434\u043b\u044f \u043f\u043e\u0447\u0442\u043e\u0432\u043e\u0433\u043e \u0441\u0435\u0440\u0432\u0435\u0440\u0430:
+password=\u041f\u0430\u0440\u043e\u043b\u044c:
+password2=(\u041f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435) \u041f\u0430\u0440\u043e\u043b\u044c:
+replyToAddress=\u0410\u0434\u0440\u0435\u0441 \u041e\u0442\u0432\u0435\u0442\u0438\u0442\u044c \u043d\u0430:
+username=\u0418\u043c\u044f:
+registration.addSubscription=\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c
+registration.deleteSubscription=\u0423\u0434\u0430\u043b\u0438\u0442\u044c
+registration.editSubscription=\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c
+registration.title.create=\u0417\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f
+registration.title.edit=\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0434\u0430\u043d\u043d\u044b\u0435 \u043e \u0441\u0432\u043e\u0435\u0439 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438
+subscription.title.create=\u0421\u043e\u0437\u0434\u0430\u0442\u044c \u043d\u043e\u0432\u0443\u044e \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0443
+subscription.title.delete=\u0423\u0434\u0430\u043b\u0438\u0442\u044c \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0443
+subscription.title.edit=\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0443\u044e \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0443
+
+# Standard error messages for validator framework checks
+errors.required=${getText(fieldName)} is required.
+errors.minlength=${getText(fieldName)} cannot be less than {1} characters.
+errors.maxlength=${getText(fieldName)} cannot be greater than {2} characters.
+errors.invalid=${getText(fieldName)} is invalid.
+errors.byte=${getText(fieldName)} must be an byte.
+errors.short=${getText(fieldName)} must be an short.
+errors.integer=${getText(fieldName)} must be an integer.
+errors.long=${getText(fieldName)} must be an long.
+errors.float=${getText(fieldName)} must be an float.
+errors.double=${getText(fieldName)} must be an double.
+errors.date=${getText(fieldName)} is not a date.
+errors.range=${getText(fieldName)} is not in the range {1} through {2}.
+errors.creditcard=${getText(fieldName)} is not a valid credit card number.
+errors.email=${getText(fieldName)} is an invalid e-mail address.

http://git-wip-us.apache.org/repos/asf/struts-examples/blob/0677ec5c/mailreader/src/main/java/mailreader2/Registration-Registration_save-validation.xml
----------------------------------------------------------------------
diff --git a/mailreader/src/main/java/mailreader2/Registration-Registration_save-validation.xml b/mailreader/src/main/java/mailreader2/Registration-Registration_save-validation.xml
new file mode 100644
index 0000000..509ca5f
--- /dev/null
+++ b/mailreader/src/main/java/mailreader2/Registration-Registration_save-validation.xml
@@ -0,0 +1,28 @@
+<!DOCTYPE validators PUBLIC "-//Apache Struts//XWork Validator 1.0.2//EN" "http://struts.apache.org/dtds/xwork-validator-1.0.2.dtd">
+
+<validators>
+
+    <field name="password">
+        <field-validator type="requiredstring">
+            <message key="error.password.required"/>
+        </field-validator>
+        <field-validator type="stringlength">
+            <param name="trim">true</param>
+            <param name="minLength">4</param>
+            <param name="maxLength">10</param>
+            <message key="errors.range"/>
+        </field-validator>
+    </field>
+
+    <field name="password2">
+        <field-validator type="requiredstring">
+            <message key="error.password2.required"/>
+        </field-validator>
+    </field>
+
+    <validator type="expression">
+        <param name="expression">password eq password2</param>
+        <message key="error.password.match"/>
+    </validator>
+
+</validators>

http://git-wip-us.apache.org/repos/asf/struts-examples/blob/0677ec5c/mailreader/src/main/java/mailreader2/Registration-validation.xml
----------------------------------------------------------------------
diff --git a/mailreader/src/main/java/mailreader2/Registration-validation.xml b/mailreader/src/main/java/mailreader2/Registration-validation.xml
new file mode 100644
index 0000000..a123125
--- /dev/null
+++ b/mailreader/src/main/java/mailreader2/Registration-validation.xml
@@ -0,0 +1,32 @@
+<!DOCTYPE validators PUBLIC "-//Apache Struts//XWork Validator 1.0.2//EN" "http://struts.apache.org/dtds/xwork-validator-1.0.2.dtd">
+
+<validators>
+
+    <field name="username">
+        <field-validator type="requiredstring">
+            <message key="error.username.required"/>
+        </field-validator>
+    </field>
+
+    <field name="user.fullName">
+        <field-validator type="requiredstring">
+            <message key="error.fullName.required"/>
+        </field-validator>
+    </field>
+
+    <field name="user.fromAddress">
+        <field-validator type="requiredstring">
+            <message key="error.fromAddress.required"/>
+        </field-validator>
+        <field-validator type="email">
+            <message key="errors.email"/>
+        </field-validator>
+    </field>
+
+    <field name="user.replyToAddress">
+        <field-validator type="email">
+            <message key="errors.email"/>
+        </field-validator>
+    </field>
+
+</validators>

http://git-wip-us.apache.org/repos/asf/struts-examples/blob/0677ec5c/mailreader/src/main/java/mailreader2/Registration.java
----------------------------------------------------------------------
diff --git a/mailreader/src/main/java/mailreader2/Registration.java b/mailreader/src/main/java/mailreader2/Registration.java
new file mode 100644
index 0000000..635fc0d
--- /dev/null
+++ b/mailreader/src/main/java/mailreader2/Registration.java
@@ -0,0 +1,122 @@
+/*
+ * $Id$
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package mailreader2;
+
+import org.apache.struts.apps.mailreader.dao.User;
+
+
+/**
+ * <p>Insert or update a User object to the persistent store. </p>
+ */
+public class Registration extends MailreaderSupport {
+
+    /**
+     * <p>Double check that there is not a valid User login. </p>
+     *
+     * @return True if there is not a valid User login
+     */
+    private boolean isCreating() {
+        User user = getUser();
+        return (null == user) || (null == user.getDatabase());
+    }
+
+    /**
+     * <p> Retrieve User object to edit or null if User does not exist. </p>
+     *
+     * @return The "Success" result for this mapping
+     * @throws Exception on any error
+     */
+    public String input() throws Exception {
+
+        if (isCreating()) {
+            createInputUser();
+            setTask(Constants.CREATE);
+        } else {
+            setTask(Constants.EDIT);
+            setUsername(getUser().getUsername());
+            setPassword(getUser().getPassword());
+            setPassword2(getUser().getPassword());
+        }
+
+        return INPUT;
+    }
+
+    /**
+     * <p>Insert or update a Registration.</p>
+     *
+     * @return The "outcome" result code
+     * @throws Exception on any error
+     */
+    public String save() throws Exception {
+        return execute();
+    }
+
+    /**
+     * <p> Insert or update a User object to the persistent store. </p>
+     * <p/>
+     * <p> If a User is not logged in, then a new User is created and
+     * automatically logged in. Otherwise, the existing User is updated. </p>
+     *
+     * @return The "outcome" result code
+     * @throws Exception on any error
+     */
+    public String execute()
+            throws Exception {
+
+        boolean creating = Constants.CREATE.equals(getTask());
+        creating = creating && isCreating(); // trust but verify
+
+        if (creating) {
+
+            User user = findUser(getUsername(), getPassword());
+            boolean haveUser = (user != null);
+
+            if (haveUser) {
+                addActionError(getText("error.username.unique"));
+                return INPUT;
+            }
+
+            copyUser(getUsername(), getPassword());
+
+        } else {
+
+            // FIXME: Any way to call the RegisrationSave validators from here?
+            String newPassword = getPassword();
+            if (newPassword != null) {
+                String confirmPassword = getPassword2();
+                boolean matches = ((null != confirmPassword)
+                        && (confirmPassword.equals(newPassword)));
+                if (matches) {
+                    getUser().setPassword(newPassword);
+                } else {
+                    addActionError(getText("error.password.match"));
+                    return INPUT;
+                }
+            }
+        }
+
+        saveUser();
+
+        return SUCCESS;
+    }
+
+}