You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jspwiki.apache.org by ju...@apache.org on 2020/02/24 16:52:57 UTC

[jspwiki] 15/38: JSPWIKI-120: rename + extract interface from UserManager

This is an automated email from the ASF dual-hosted git repository.

juanpablo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/jspwiki.git

commit 11a0ada483246feb26f70a563c5a53aa29e0218e
Author: juanpablo <ju...@apache.org>
AuthorDate: Thu Feb 20 20:16:19 2020 +0100

    JSPWIKI-120: rename + extract interface from UserManager
---
 .../{UserManager.java => DefaultUserManager.java}  | 383 +++--------
 .../java/org/apache/wiki/auth/UserManager.java     | 712 ++-------------------
 .../wiki/auth/user/AbstractUserDatabase.java       | 169 ++---
 .../apache/wiki/auth/user/JDBCUserDatabase.java    | 143 ++---
 .../org/apache/wiki/auth/user/UserDatabase.java    | 172 ++---
 .../org/apache/wiki/auth/user/XMLUserDatabase.java | 287 ++++-----
 .../org/apache/wiki/tasks/DefaultTasksManager.java |  10 +-
 .../java/org/apache/wiki/tasks/TasksManager.java   |   4 +-
 .../wiki/tasks/auth/SaveUserProfileTask.java       |  18 +-
 .../org/apache/wiki/workflow/WorkflowBuilder.java  |  89 +--
 .../src/main/resources/ini/classmappings.xml       |   2 +-
 11 files changed, 546 insertions(+), 1443 deletions(-)

diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/UserManager.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultUserManager.java
similarity index 59%
copy from jspwiki-main/src/main/java/org/apache/wiki/auth/UserManager.java
copy to jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultUserManager.java
index 5d51ec4..c2b13af 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/auth/UserManager.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/DefaultUserManager.java
@@ -21,11 +21,11 @@ package org.apache.wiki.auth;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.log4j.Logger;
 import org.apache.wiki.WikiContext;
-import org.apache.wiki.WikiEngine;
 import org.apache.wiki.WikiSession;
 import org.apache.wiki.ajax.AjaxUtil;
 import org.apache.wiki.ajax.WikiAjaxDispatcherServlet;
 import org.apache.wiki.ajax.WikiAjaxServlet;
+import org.apache.wiki.api.core.Engine;
 import org.apache.wiki.api.engine.FilterManager;
 import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
 import org.apache.wiki.api.exceptions.WikiException;
@@ -41,7 +41,9 @@ import org.apache.wiki.event.WikiEventManager;
 import org.apache.wiki.event.WikiSecurityEvent;
 import org.apache.wiki.filters.SpamFilter;
 import org.apache.wiki.i18n.InternationalizationManager;
+import org.apache.wiki.pages.PageManager;
 import org.apache.wiki.preferences.Preferences;
+import org.apache.wiki.tasks.TasksManager;
 import org.apache.wiki.ui.InputValidator;
 import org.apache.wiki.util.ClassUtil;
 import org.apache.wiki.util.TextUtil;
@@ -69,10 +71,11 @@ import java.util.WeakHashMap;
 
 
 /**
- * Provides a facade for obtaining user information.
+ * Default implementation for {@link UserManager}.
+ *
  * @since 2.3
  */
-public class UserManager {
+public class DefaultUserManager implements UserManager {
 
     private static final String USERDATABASE_PACKAGE = "org.apache.wiki.auth.user";
     private static final String SESSION_MESSAGES = "profile";
@@ -82,47 +85,31 @@ public class UserManager {
     private static final String PARAM_LOGINNAME = "loginname";
     private static final String UNKNOWN_CLASS = "<unknown>";
 
-    private WikiEngine m_engine;
+    private Engine m_engine;
 
-    private static final Logger log = Logger.getLogger(UserManager.class);
-
-    /** Message key for the "save profile" message. */
-    private static final String PROP_DATABASE               = "jspwiki.userdatabase";
-
-    public static final String JSON_USERS = "users";
-
-    // private static final String  PROP_ACLMANAGER     = "jspwiki.aclManager";
+    private static final Logger log = Logger.getLogger( DefaultUserManager.class);
 
     /** Associates wiki sessions with profiles */
-    private final Map<WikiSession,UserProfile> m_profiles = new WeakHashMap<>();
+    private final Map< WikiSession, UserProfile > m_profiles = new WeakHashMap<>();
 
     /** The user database loads, manages and persists user identities */
-    private UserDatabase     m_database;
+    private UserDatabase m_database;
 
-    /**
-     * Initializes the engine for its nefarious purposes.
-     * @param engine the current wiki engine
-     * @param props the wiki engine initialization properties
-     */
-    public void initialize( final WikiEngine engine, final Properties props ) {
+    /** {@inheritDoc} */
+    @Override
+    public void initialize( final Engine engine, final Properties props ) {
         m_engine = engine;
 
         // Attach the PageManager as a listener
         // TODO: it would be better if we did this in PageManager directly
-        addWikiEventListener( engine.getPageManager() );
+        addWikiEventListener( engine.getManager( PageManager.class ) );
 
         //TODO: Replace with custom annotations. See JSPWIKI-566
         WikiAjaxDispatcherServlet.registerServlet( JSON_USERS, new JSONUserModule(this), new AllPermission(null));
     }
 
-    /**
-     * Returns the UserDatabase employed by this WikiEngine. The UserDatabase is lazily initialized by this method, if it does
-     * not exist yet. If the initialization fails, this method will use the inner class DummyUserDatabase as a default (which
-     * is enough to get JSPWiki running).
-     *
-     * @return the dummy user database
-     * @since 2.3
-     */
+    /** {@inheritDoc} */
+    @Override
     public UserDatabase getUserDatabase() {
         if( m_database != null ) {
             return m_database;
@@ -158,29 +145,8 @@ public class UserManager {
         return m_database;
     }
 
-    /**
-     * <p>Retrieves the {@link org.apache.wiki.auth.user.UserProfile}for the
-     * user in a wiki session. If the user is authenticated, the UserProfile
-     * returned will be the one stored in the user database; if one does not
-     * exist, a new one will be initialized and returned. If the user is
-     * anonymous or asserted, the UserProfile will <i>always</i> be newly
-     * initialized to prevent spoofing of identities. If a UserProfile needs to
-     * be initialized, its
-     * {@link org.apache.wiki.auth.user.UserProfile#isNew()} method will
-     * return <code>true</code>, and its login name will will be set
-     * automatically if the user is authenticated. Note that this method does
-     * not modify the retrieved (or newly created) profile otherwise; other
-     * fields in the user profile may be <code>null</code>.</p>
-     * <p>If a new UserProfile was created, but its
-     * {@link org.apache.wiki.auth.user.UserProfile#isNew()} method returns
-     * <code>false</code>, this method throws an {@link IllegalStateException}.
-     * This is meant as a quality check for UserDatabase providers;
-     * it should only be thrown if the implementation is faulty.</p>
-     * @param session the wiki session, which may not be <code>null</code>
-     * @return the user's profile, which will be newly initialized if the user
-     * is anonymous or asserted, or if the user cannot be found in the user
-     * database
-     */
+    /** {@inheritDoc} */
+    @Override
     public UserProfile getUserProfile( final WikiSession session ) {
         // Look up cached user profile
         UserProfile profile = m_profiles.get( session );
@@ -211,48 +177,12 @@ public class UserManager {
         return profile;
     }
 
-    /**
-     * <p>
-     * Saves the {@link org.apache.wiki.auth.user.UserProfile}for the user in
-     * a wiki session. This method verifies that a user profile to be saved
-     * doesn't collide with existing profiles; that is, the login name
-     * or full name is already used by another profile. If the profile
-     * collides, a <code>DuplicateUserException</code> is thrown. After saving
-     * the profile, the user database changes are committed, and the user's
-     * credential set is refreshed; if custom authentication is used, this means
-     * the user will be automatically be logged in.
-     * </p>
-     * <p>
-     * When the user's profile is saved successfully, this method fires a
-     * {@link WikiSecurityEvent#PROFILE_SAVE} event with the WikiSession as the
-     * source and the UserProfile as target. For existing profiles, if the
-     * user's full name changes, this method also fires a "name changed"
-     * event ({@link WikiSecurityEvent#PROFILE_NAME_CHANGED}) with the
-     * WikiSession as the source and an array containing the old and new
-     * UserProfiles, respectively. The <code>NAME_CHANGED</code> event allows
-     * the GroupManager and PageManager can change group memberships and
-     * ACLs if needed.
-     * </p>
-     * <p>
-     * Note that WikiSessions normally attach event listeners to the
-     * UserManager, so changes to the profile will automatically cause the
-     * correct Principals to be reloaded into the current WikiSession's Subject.
-     * </p>
-     * @param session the wiki session, which may not be <code>null</code>
-     * @param profile the user profile, which may not be <code>null</code>
-     * @throws DuplicateUserException if the proposed profile's login name or full name collides with another
-     * @throws WikiException if the save fails for some reason. If the current user does not have
-     * permission to save the profile, this will be a {@link org.apache.wiki.auth.WikiSecurityException};
-     * if if the user profile must be approved before it can be saved, it will be a
-     * {@link org.apache.wiki.workflow.DecisionRequiredException}. All other WikiException
-     * indicate a condition that is not normal is probably due to mis-configuration
-     */
-    public void setUserProfile( WikiSession session, UserProfile profile ) throws DuplicateUserException, WikiException
-    {
+    /** {@inheritDoc} */
+    @Override
+    public void setUserProfile( final WikiSession session, final UserProfile profile ) throws DuplicateUserException, WikiException {
         // Verify user is allowed to save profile!
         final Permission p = new WikiPermission( m_engine.getApplicationName(), WikiPermission.EDIT_PROFILE_ACTION );
-        if ( !m_engine.getAuthorizationManager().checkPermission( session, p ) )
-        {
+        if ( !m_engine.getManager( AuthorizationManager.class ).checkPermission( session, p ) ) {
             throw new WikiSecurityException( "You are not allowed to save wiki profiles." );
         }
 
@@ -265,139 +195,97 @@ public class UserManager {
                                     !( oldProfile.getFullname().equals( profile.getFullname() ) &&
                                     oldProfile.getLoginName().equals( profile.getLoginName() ) );
         UserProfile otherProfile;
-        try
-        {
+        try {
             otherProfile = getUserDatabase().findByLoginName( profile.getLoginName() );
-            if ( otherProfile != null && !otherProfile.equals( oldProfile ) )
-            {
+            if( otherProfile != null && !otherProfile.equals( oldProfile ) ) {
                 throw new DuplicateUserException( "security.error.login.taken", profile.getLoginName() );
             }
+        } catch( final NoSuchPrincipalException e ) {
         }
-        catch( final NoSuchPrincipalException e )
-        {
-        }
-        try
-        {
+        try {
             otherProfile = getUserDatabase().findByFullName( profile.getFullname() );
-            if ( otherProfile != null && !otherProfile.equals( oldProfile ) )
-            {
+            if( otherProfile != null && !otherProfile.equals( oldProfile ) ) {
                 throw new DuplicateUserException( "security.error.fullname.taken", profile.getFullname() );
             }
-        }
-        catch( final NoSuchPrincipalException e )
-        {
+        } catch( final NoSuchPrincipalException e ) {
         }
 
         // For new accounts, create approval workflow for user profile save.
-        if ( newProfile && oldProfile != null && oldProfile.isNew() )
-        {
-            startUserProfileCreationWorkflow(session, profile);
+        if( newProfile && oldProfile != null && oldProfile.isNew() ) {
+            startUserProfileCreationWorkflow( session, profile );
 
             // If the profile doesn't need approval, then just log the user in
 
-            try
-            {
-                final AuthenticationManager mgr = m_engine.getAuthenticationManager();
-                if ( newProfile && !mgr.isContainerAuthenticated() )
-                {
+            try {
+                final AuthenticationManager mgr = m_engine.getManager( AuthenticationManager.class );
+                if( !mgr.isContainerAuthenticated() ) {
                     mgr.login( session, null, profile.getLoginName(), profile.getPassword() );
                 }
-            }
-            catch ( final WikiException e )
-            {
+            } catch( final WikiException e ) {
                 throw new WikiSecurityException( e.getMessage(), e );
             }
 
             // Alert all listeners that the profile changed...
             // ...this will cause credentials to be reloaded in the wiki session
             fireEvent( WikiSecurityEvent.PROFILE_SAVE, session, profile );
-        }
-
-        // For existing accounts, just save the profile
-        else
-        {
+        } else { // For existing accounts, just save the profile
             // If login name changed, rename it first
-            if ( nameChanged && oldProfile != null && !oldProfile.getLoginName().equals( profile.getLoginName() ) )
-            {
+            if( nameChanged && !oldProfile.getLoginName().equals( profile.getLoginName() ) ) {
                 getUserDatabase().rename( oldProfile.getLoginName(), profile.getLoginName() );
             }
 
             // Now, save the profile (userdatabase will take care of timestamps for us)
             getUserDatabase().save( profile );
 
-            if ( nameChanged )
-            {
+            if( nameChanged ) {
                 // Fire an event if the login name or full name changed
                 final UserProfile[] profiles = new UserProfile[] { oldProfile, profile };
                 fireEvent( WikiSecurityEvent.PROFILE_NAME_CHANGED, session, profiles );
-            }
-            else
-            {
+            } else {
                 // Fire an event that says we have new a new profile (new principals)
                 fireEvent( WikiSecurityEvent.PROFILE_SAVE, session, profile );
             }
         }
     }
 
+    /** {@inheritDoc} */
+    @Override
     public void startUserProfileCreationWorkflow( final WikiSession session, final UserProfile profile ) throws WikiException {
         final WorkflowBuilder builder = WorkflowBuilder.getBuilder( m_engine );
         final Principal submitter = session.getUserPrincipal();
-        final Step completionTask = m_engine.getTasksManager().buildSaveUserProfileTask( m_engine, session.getLocale() );
+        final Step completionTask = m_engine.getManager( TasksManager.class ).buildSaveUserProfileTask( m_engine, session.getLocale() );
 
         // Add user profile attribute as Facts for the approver (if required)
         final boolean hasEmail = profile.getEmail() != null;
-        final Fact[] facts = new Fact[ hasEmail ? 4 : 3];
-        facts[0] = new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_PREFS_FULL_NAME, profile.getFullname() );
-        facts[1] = new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_PREFS_LOGIN_NAME, profile.getLoginName() );
-        facts[2] = new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_SUBMITTER, submitter.getName() );
-        if ( hasEmail )
-        {
-            facts[3] = new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_PREFS_EMAIL, profile.getEmail() );
-        }
-        final Workflow workflow = builder.buildApprovalWorkflow( submitter, 
-                                                                 WorkflowManager.WF_UP_CREATE_SAVE_APPROVER, 
-                                                                 null, 
-                                                                 WorkflowManager.WF_UP_CREATE_SAVE_DECISION_MESSAGE_KEY, 
-                                                                 facts, 
-                                                                 completionTask, 
+        final Fact[] facts = new Fact[ hasEmail ? 4 : 3 ];
+        facts[ 0 ] = new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_PREFS_FULL_NAME, profile.getFullname() );
+        facts[ 1 ] = new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_PREFS_LOGIN_NAME, profile.getLoginName() );
+        facts[ 2 ] = new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_SUBMITTER, submitter.getName() );
+        if ( hasEmail ) {
+            facts[ 3 ] = new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_PREFS_EMAIL, profile.getEmail() );
+        }
+        final Workflow workflow = builder.buildApprovalWorkflow( submitter,
+                                                                 WorkflowManager.WF_UP_CREATE_SAVE_APPROVER,
+                                                                 null,
+                                                                 WorkflowManager.WF_UP_CREATE_SAVE_DECISION_MESSAGE_KEY,
+                                                                 facts,
+                                                                 completionTask,
                                                                  null );
 
         workflow.setAttribute( WorkflowManager.WF_UP_CREATE_SAVE_ATTR_SAVED_PROFILE, profile );
-        m_engine.getWorkflowManager().start(workflow);
+        m_engine.getManager( WorkflowManager.class ).start( workflow );
 
         final boolean approvalRequired = workflow.getCurrentStep() instanceof Decision;
 
         // If the profile requires approval, redirect user to message page
-        if ( approvalRequired )
-        {
+        if ( approvalRequired ) {
             throw new DecisionRequiredException( "This profile must be approved before it becomes active" );
         }
     }
 
-    /**
-     * <p> Extracts user profile parameters from the HTTP request and populates
-     * a UserProfile with them. The UserProfile will either be a copy of the
-     * user's existing profile (if one can be found), or a new profile (if not).
-     * The rules for populating the profile as as follows: </p> <ul> <li>If the
-     * <code>email</code> or <code>password</code> parameter values differ
-     * from those in the existing profile, the passed parameters override the
-     * old values.</li> <li>For new profiles, the user-supplied
-     * <code>fullname</code> parameter is always
-     * used; for existing profiles the existing value is used, and whatever
-     * value the user supplied is discarded. The wiki name is automatically
-     * computed by taking the full name and extracting all whitespace.</li>
-     * <li>In all cases, the
-     * created/last modified timestamps of the user's existing or new profile
-     * always override whatever values the user supplied.</li> <li>If
-     * container authentication is used, the login name property of the profile
-     * is set to the name of
-     * {@link org.apache.wiki.WikiSession#getLoginPrincipal()}. Otherwise,
-     * the value of the <code>loginname</code> parameter is used.</li> </ul>
-     * @param context the current wiki context
-     * @return a new, populated user profile
-     */
-    public UserProfile parseProfile( WikiContext context )
-    {
+    /** {@inheritDoc} */
+    @Override
+    public UserProfile parseProfile( final WikiContext context ) {
         // Retrieve the user's profile (may have been previously cached)
         final UserProfile profile = getUserProfile( context.getWikiSession() );
         final HttpServletRequest request = context.getHttpRequest();
@@ -414,9 +302,7 @@ public class UserManager {
 
         // A special case if we have container authentication
         // If authenticated, login name is always taken from container
-        if ( m_engine.getAuthenticationManager().isContainerAuthenticated() &&
-                context.getWikiSession().isAuthenticated() )
-        {
+        if ( m_engine.getManager( AuthenticationManager.class ).isContainerAuthenticated() && context.getWikiSession().isAuthenticated() ) {
             loginName = context.getWikiSession().getLoginPrincipal().getName();
         }
 
@@ -428,36 +314,20 @@ public class UserManager {
         return profile;
     }
 
-    /**
-     * Validates a user profile, and appends any errors to the session errors
-     * list. If the profile is new, the password will be checked to make sure it
-     * isn't null. Otherwise, the password is checked for length and that it
-     * matches the value of the 'password2' HTTP parameter. Note that we have a
-     * special case when container-managed authentication is used and the user
-     * is not authenticated; this will always cause validation to fail. Any
-     * validation errors are added to the wiki session's messages collection
-     * (see {@link WikiSession#getMessages()}.
-     * @param context the current wiki context
-     * @param profile the supplied UserProfile
-     */
-    public void validateProfile( WikiContext context, UserProfile profile )
-    {
+    /** {@inheritDoc} */
+    @Override
+    public void validateProfile( final WikiContext context, final UserProfile profile ) {
         final boolean isNew = profile.isNew();
         final WikiSession session = context.getWikiSession();
         final InputValidator validator = new InputValidator( SESSION_MESSAGES, context );
         final ResourceBundle rb = Preferences.getBundle( context, InternationalizationManager.CORE_BUNDLE );
 
-        //
         //  Query the SpamFilter first
-        //
-        final FilterManager fm = m_engine.getFilterManager();
-        final List<PageFilter> ls = fm.getFilterList();
-        for( final PageFilter pf : ls )
-        {
-            if( pf instanceof SpamFilter )
-            {
-                if( ((SpamFilter)pf).isValidUserProfile( context, profile ) == false )
-                {
+        final FilterManager fm = m_engine.getManager( FilterManager.class );
+        final List< PageFilter > ls = fm.getFilterList();
+        for( final PageFilter pf : ls ) {
+            if( pf instanceof SpamFilter ) {
+                if( !( ( SpamFilter )pf ).isValidUserProfile( context, profile ) ) {
                     session.addMessage( SESSION_MESSAGES, "Invalid userprofile" );
                     return;
                 }
@@ -466,9 +336,8 @@ public class UserManager {
         }
 
         // If container-managed auth and user not logged in, throw an error
-        if ( m_engine.getAuthenticationManager().isContainerAuthenticated()
-             && !context.getWikiSession().isAuthenticated() )
-        {
+        if ( m_engine.getManager( AuthenticationManager.class ).isContainerAuthenticated()
+             && !context.getWikiSession().isAuthenticated() ) {
             session.addMessage( SESSION_MESSAGES, rb.getString("security.error.createprofilebeforelogin") );
         }
 
@@ -477,23 +346,17 @@ public class UserManager {
         validator.validate( profile.getEmail(), rb.getString("security.user.email"), InputValidator.EMAIL );
 
         // If new profile, passwords must match and can't be null
-        if ( !m_engine.getAuthenticationManager().isContainerAuthenticated() )
-        {
+        if( !m_engine.getManager( AuthenticationManager.class ).isContainerAuthenticated() ) {
             final String password = profile.getPassword();
-            if ( password == null )
-            {
-                if ( isNew )
-                {
-                    session.addMessage( SESSION_MESSAGES, rb.getString("security.error.blankpassword") );
+            if( password == null ) {
+                if( isNew ) {
+                    session.addMessage( SESSION_MESSAGES, rb.getString( "security.error.blankpassword" ) );
                 }
-            }
-            else
-            {
+            } else {
                 final HttpServletRequest request = context.getHttpRequest();
                 final String password2 = ( request == null ) ? null : request.getParameter( "password2" );
-                if ( !password.equals( password2 ) )
-                {
-                    session.addMessage( SESSION_MESSAGES, rb.getString("security.error.passwordnomatch") );
+                if( !password.equals( password2 ) ) {
+                    session.addMessage( SESSION_MESSAGES, rb.getString( "security.error.passwordnomatch" ) );
                 }
             }
         }
@@ -504,56 +367,38 @@ public class UserManager {
         final String email = profile.getEmail();
 
         // It's illegal to use as a full name someone else's login name
-        try
-        {
+        try {
             otherProfile = getUserDatabase().find( fullName );
-            if ( otherProfile != null && !profile.equals( otherProfile ) && !fullName.equals( otherProfile.getFullname() ) )
-            {
+            if( otherProfile != null && !profile.equals( otherProfile ) && !fullName.equals( otherProfile.getFullname() ) ) {
                 final Object[] args = { fullName };
-                session.addMessage( SESSION_MESSAGES, MessageFormat.format( rb.getString("security.error.illegalfullname"), args ) );
+                session.addMessage( SESSION_MESSAGES, MessageFormat.format( rb.getString( "security.error.illegalfullname" ), args ) );
             }
-        }
-        catch ( final NoSuchPrincipalException e)
-        { /* It's clean */ }
+        } catch( final NoSuchPrincipalException e ) { /* It's clean */ }
 
         // It's illegal to use as a login name someone else's full name
-        try
-        {
+        try {
             otherProfile = getUserDatabase().find( loginName );
-            if ( otherProfile != null && !profile.equals( otherProfile ) && !loginName.equals( otherProfile.getLoginName() ) )
-            {
+            if( otherProfile != null && !profile.equals( otherProfile ) && !loginName.equals( otherProfile.getLoginName() ) ) {
                 final Object[] args = { loginName };
-                session.addMessage( SESSION_MESSAGES, MessageFormat.format( rb.getString("security.error.illegalloginname"), args ) );
+                session.addMessage( SESSION_MESSAGES, MessageFormat.format( rb.getString( "security.error.illegalloginname" ), args ) );
             }
-        }
-        catch ( final NoSuchPrincipalException e)
-        { /* It's clean */ }
+        } catch( final NoSuchPrincipalException e ) { /* It's clean */ }
 
         // It's illegal to use multiple accounts with the same email
-        try
-        {
+        try {
             otherProfile = getUserDatabase().findByEmail( email );
-            if ( otherProfile != null
-                && !profile.getUid().equals(otherProfile.getUid()) // Issue JSPWIKI-1042
-                && !profile.equals( otherProfile ) && StringUtils.lowerCase( email ).equals( StringUtils.lowerCase(otherProfile.getEmail() ) ) )
-            {
+            if( otherProfile != null && !profile.getUid().equals( otherProfile.getUid() ) // Issue JSPWIKI-1042
+                    && !profile.equals( otherProfile ) && StringUtils.lowerCase( email )
+                    .equals( StringUtils.lowerCase( otherProfile.getEmail() ) ) ) {
                 final Object[] args = { email };
-                session.addMessage( SESSION_MESSAGES, MessageFormat.format( rb.getString("security.error.email.taken"), args ) );
+                session.addMessage( SESSION_MESSAGES, MessageFormat.format( rb.getString( "security.error.email.taken" ), args ) );
             }
-        }
-        catch ( final NoSuchPrincipalException e)
-        { /* It's clean */ }
+        } catch( final NoSuchPrincipalException e ) { /* It's clean */ }
     }
 
-    /**
-     *  A helper method for returning all of the known WikiNames in this system.
-     *
-     *  @return An Array of Principals
-     *  @throws WikiSecurityException If for reason the names cannot be fetched
-     */
-    public Principal[] listWikiNames()
-        throws WikiSecurityException
-    {
+    /** {@inheritDoc} */
+    @Override
+    public Principal[] listWikiNames() throws WikiSecurityException {
         return getUserDatabase().getWikiNames();
     }
 
@@ -568,7 +413,7 @@ public class UserManager {
          * @param loginName the login name to delete
          */
         @Override
-        public void deleteByLoginName( String loginName ) {
+        public void deleteByLoginName( final String loginName ) {
             // No operation
         }
 
@@ -642,7 +487,7 @@ public class UserManager {
          * @param props the properties used to initialize the wiki engine
          */
         @Override
-        public void initialize(final WikiEngine engine, final Properties props) {
+        public void initialize( final Engine engine, final Properties props ) {
         }
 
         /**
@@ -673,8 +518,7 @@ public class UserManager {
      * This is a convenience method.
      * @param listener the event listener
      */
-    public synchronized void addWikiEventListener( WikiEventListener listener )
-    {
+    @Override public synchronized void addWikiEventListener( final WikiEventListener listener ) {
         WikiEventManager.addWikiEventListener( this, listener );
     }
 
@@ -683,29 +527,11 @@ public class UserManager {
      * This is a convenience method.
      * @param listener the event listener
      */
-    public synchronized void removeWikiEventListener( WikiEventListener listener )
-    {
+    @Override public synchronized void removeWikiEventListener( final WikiEventListener listener ) {
         WikiEventManager.removeWikiEventListener( this, listener );
     }
 
     /**
-     *  Fires a WikiSecurityEvent of the provided type, Principal and target Object
-     *  to all registered listeners.
-     *
-     * @see org.apache.wiki.event.WikiSecurityEvent
-     * @param type       the event type to be fired
-     * @param session    the wiki session supporting the event
-     * @param profile    the user profile (or array of user profiles), which may be <code>null</code>
-     */
-    protected void fireEvent( int type, WikiSession session, Object profile )
-    {
-        if ( WikiEventManager.isListening(this) )
-        {
-            WikiEventManager.fireEvent(this,new WikiSecurityEvent(session,type,profile));
-        }
-    }
-
-    /**
      *  Implements the JSON API for usermanager.
      *  <p>
      *  Even though this gets serialized whenever container shuts down/restarts, this gets reinstalled to the session when JSPWiki starts.
@@ -713,13 +539,13 @@ public class UserManager {
      */
     public static final class JSONUserModule implements WikiAjaxServlet {
 
-		private volatile UserManager m_manager;
+		private volatile DefaultUserManager m_manager;
 
         /**
          *  Create a new JSONUserModule.
          *  @param mgr Manager
          */
-        public JSONUserModule( UserManager mgr )
+        public JSONUserModule( final DefaultUserManager mgr )
         {
             m_manager = mgr;
         }
@@ -730,7 +556,7 @@ public class UserManager {
         }
 
         @Override
-        public void service(HttpServletRequest req, HttpServletResponse resp, String actionName, List<String> params) throws ServletException, IOException {
+        public void service( final HttpServletRequest req, final HttpServletResponse resp, final String actionName, final List<String> params) throws ServletException, IOException {
         	try {
             	if( params.size() < 1 ) {
             		return;
@@ -753,13 +579,12 @@ public class UserManager {
          *  @return A UserProfile object
          *  @throws NoSuchPrincipalException If such a name does not exist.
          */
-        public UserProfile getUserInfo( String uid ) throws NoSuchPrincipalException {
+        public UserProfile getUserInfo( final String uid ) throws NoSuchPrincipalException {
             if( m_manager != null ) {
-                final UserProfile prof = m_manager.getUserDatabase().find( uid );
-                return prof;
+                return m_manager.getUserDatabase().find( uid );
             }
 
-            throw new IllegalStateException("The manager is offline.");
+            throw new IllegalStateException( "The manager is offline." );
         }
     }
 
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/UserManager.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/UserManager.java
index 5d51ec4..c59130b 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/auth/UserManager.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/UserManager.java
@@ -18,102 +18,40 @@
  */
 package org.apache.wiki.auth;
 
-import org.apache.commons.lang3.StringUtils;
-import org.apache.log4j.Logger;
 import org.apache.wiki.WikiContext;
-import org.apache.wiki.WikiEngine;
 import org.apache.wiki.WikiSession;
-import org.apache.wiki.ajax.AjaxUtil;
-import org.apache.wiki.ajax.WikiAjaxDispatcherServlet;
-import org.apache.wiki.ajax.WikiAjaxServlet;
-import org.apache.wiki.api.engine.FilterManager;
-import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
+import org.apache.wiki.api.core.Engine;
 import org.apache.wiki.api.exceptions.WikiException;
-import org.apache.wiki.api.filters.PageFilter;
-import org.apache.wiki.auth.permissions.AllPermission;
-import org.apache.wiki.auth.permissions.WikiPermission;
-import org.apache.wiki.auth.user.AbstractUserDatabase;
 import org.apache.wiki.auth.user.DuplicateUserException;
 import org.apache.wiki.auth.user.UserDatabase;
 import org.apache.wiki.auth.user.UserProfile;
 import org.apache.wiki.event.WikiEventListener;
 import org.apache.wiki.event.WikiEventManager;
 import org.apache.wiki.event.WikiSecurityEvent;
-import org.apache.wiki.filters.SpamFilter;
-import org.apache.wiki.i18n.InternationalizationManager;
-import org.apache.wiki.preferences.Preferences;
-import org.apache.wiki.ui.InputValidator;
-import org.apache.wiki.util.ClassUtil;
-import org.apache.wiki.util.TextUtil;
-import org.apache.wiki.workflow.Decision;
-import org.apache.wiki.workflow.DecisionRequiredException;
-import org.apache.wiki.workflow.Fact;
-import org.apache.wiki.workflow.Step;
-import org.apache.wiki.workflow.Workflow;
-import org.apache.wiki.workflow.WorkflowBuilder;
-import org.apache.wiki.workflow.WorkflowManager;
 
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.io.IOException;
-import java.security.Permission;
 import java.security.Principal;
-import java.text.MessageFormat;
-import java.util.List;
-import java.util.Map;
-import java.util.NoSuchElementException;
 import java.util.Properties;
-import java.util.ResourceBundle;
-import java.util.WeakHashMap;
 
 
 /**
  * Provides a facade for obtaining user information.
+ *
  * @since 2.3
  */
-public class UserManager {
-
-    private static final String USERDATABASE_PACKAGE = "org.apache.wiki.auth.user";
-    private static final String SESSION_MESSAGES = "profile";
-    private static final String PARAM_EMAIL = "email";
-    private static final String PARAM_FULLNAME = "fullname";
-    private static final String PARAM_PASSWORD = "password";
-    private static final String PARAM_LOGINNAME = "loginname";
-    private static final String UNKNOWN_CLASS = "<unknown>";
-
-    private WikiEngine m_engine;
-
-    private static final Logger log = Logger.getLogger(UserManager.class);
+public interface UserManager {
 
     /** Message key for the "save profile" message. */
-    private static final String PROP_DATABASE               = "jspwiki.userdatabase";
-
-    public static final String JSON_USERS = "users";
+    String PROP_DATABASE = "jspwiki.userdatabase";
 
-    // private static final String  PROP_ACLMANAGER     = "jspwiki.aclManager";
-
-    /** Associates wiki sessions with profiles */
-    private final Map<WikiSession,UserProfile> m_profiles = new WeakHashMap<>();
-
-    /** The user database loads, manages and persists user identities */
-    private UserDatabase     m_database;
+    String JSON_USERS = "users";
 
     /**
      * Initializes the engine for its nefarious purposes.
+     *
      * @param engine the current wiki engine
      * @param props the wiki engine initialization properties
      */
-    public void initialize( final WikiEngine engine, final Properties props ) {
-        m_engine = engine;
-
-        // Attach the PageManager as a listener
-        // TODO: it would be better if we did this in PageManager directly
-        addWikiEventListener( engine.getPageManager() );
-
-        //TODO: Replace with custom annotations. See JSPWIKI-566
-        WikiAjaxDispatcherServlet.registerServlet( JSON_USERS, new JSONUserModule(this), new AllPermission(null));
-    }
+    void initialize( final Engine engine, final Properties props );
 
     /**
      * Returns the UserDatabase employed by this WikiEngine. The UserDatabase is lazily initialized by this method, if it does
@@ -123,121 +61,44 @@ public class UserManager {
      * @return the dummy user database
      * @since 2.3
      */
-    public UserDatabase getUserDatabase() {
-        if( m_database != null ) {
-            return m_database;
-        }
-
-        String dbClassName = UNKNOWN_CLASS;
-
-        try {
-            dbClassName = TextUtil.getRequiredProperty( m_engine.getWikiProperties(), PROP_DATABASE );
-
-            log.info( "Attempting to load user database class " + dbClassName );
-            final Class<?> dbClass = ClassUtil.findClass( USERDATABASE_PACKAGE, dbClassName );
-            m_database = (UserDatabase) dbClass.newInstance();
-            m_database.initialize( m_engine, m_engine.getWikiProperties() );
-            log.info("UserDatabase initialized.");
-        } catch( final NoSuchElementException | NoRequiredPropertyException e ) {
-            log.error( "You have not set the '"+PROP_DATABASE+"'. You need to do this if you want to enable user management by JSPWiki.", e );
-        } catch( final ClassNotFoundException e ) {
-            log.error( "UserDatabase class " + dbClassName + " cannot be found", e );
-        } catch( final InstantiationException e ) {
-            log.error( "UserDatabase class " + dbClassName + " cannot be created", e );
-        } catch( final IllegalAccessException e ) {
-            log.error( "You are not allowed to access this user database class", e );
-        } catch( final WikiSecurityException e ) {
-            log.error( "Exception initializing user database: " + e.getMessage(), e );
-        } finally {
-            if( m_database == null ) {
-                log.info("I could not create a database object you specified (or didn't specify), so I am falling back to a default.");
-                m_database = new DummyUserDatabase();
-            }
-        }
-
-        return m_database;
-    }
+    UserDatabase getUserDatabase();
 
     /**
-     * <p>Retrieves the {@link org.apache.wiki.auth.user.UserProfile}for the
-     * user in a wiki session. If the user is authenticated, the UserProfile
-     * returned will be the one stored in the user database; if one does not
-     * exist, a new one will be initialized and returned. If the user is
-     * anonymous or asserted, the UserProfile will <i>always</i> be newly
-     * initialized to prevent spoofing of identities. If a UserProfile needs to
-     * be initialized, its
-     * {@link org.apache.wiki.auth.user.UserProfile#isNew()} method will
-     * return <code>true</code>, and its login name will will be set
-     * automatically if the user is authenticated. Note that this method does
-     * not modify the retrieved (or newly created) profile otherwise; other
-     * fields in the user profile may be <code>null</code>.</p>
-     * <p>If a new UserProfile was created, but its
-     * {@link org.apache.wiki.auth.user.UserProfile#isNew()} method returns
-     * <code>false</code>, this method throws an {@link IllegalStateException}.
-     * This is meant as a quality check for UserDatabase providers;
+     * <p>Retrieves the {@link org.apache.wiki.auth.user.UserProfile} for the user in a wiki session. If the user is authenticated, the
+     * UserProfile returned will be the one stored in the user database; if one does not exist, a new one will be initialized and returned.
+     * If the user is anonymous or asserted, the UserProfile will <i>always</i> be newly initialized to prevent spoofing of identities.
+     * If a UserProfile needs to be initialized, its {@link org.apache.wiki.auth.user.UserProfile#isNew()} method will return
+     * <code>true</code>, and its login name will will be set automatically if the user is authenticated. Note that this method does
+     * not modify the retrieved (or newly created) profile otherwise; other fields in the user profile may be <code>null</code>.</p>
+     * <p>If a new UserProfile was created, but its {@link org.apache.wiki.auth.user.UserProfile#isNew()} method returns
+     * <code>false</code>, this method throws an {@link IllegalStateException}. This is meant as a quality check for UserDatabase providers;
      * it should only be thrown if the implementation is faulty.</p>
+     *
      * @param session the wiki session, which may not be <code>null</code>
-     * @return the user's profile, which will be newly initialized if the user
-     * is anonymous or asserted, or if the user cannot be found in the user
-     * database
+     * @return the user's profile, which will be newly initialized if the user is anonymous or asserted, or if the user cannot be found in
+     *         the user database
      */
-    public UserProfile getUserProfile( final WikiSession session ) {
-        // Look up cached user profile
-        UserProfile profile = m_profiles.get( session );
-        boolean newProfile = profile == null;
-        Principal user = null;
-
-        // If user is authenticated, figure out if this is an existing profile
-        if ( session.isAuthenticated() ) {
-            user = session.getUserPrincipal();
-            try {
-                profile = getUserDatabase().find( user.getName() );
-                newProfile = false;
-            } catch( final NoSuchPrincipalException e ) { }
-        }
-
-        if ( newProfile ) {
-            profile = getUserDatabase().newProfile();
-            if ( user != null ) {
-                profile.setLoginName( user.getName() );
-            }
-            if ( !profile.isNew() ) {
-                throw new IllegalStateException( "New profile should be marked 'new'. Check your UserProfile implementation." );
-            }
-        }
-
-        // Stash the profile for next time
-        m_profiles.put( session, profile );
-        return profile;
-    }
+    UserProfile getUserProfile( WikiSession session );
 
     /**
      * <p>
-     * Saves the {@link org.apache.wiki.auth.user.UserProfile}for the user in
-     * a wiki session. This method verifies that a user profile to be saved
-     * doesn't collide with existing profiles; that is, the login name
-     * or full name is already used by another profile. If the profile
-     * collides, a <code>DuplicateUserException</code> is thrown. After saving
-     * the profile, the user database changes are committed, and the user's
-     * credential set is refreshed; if custom authentication is used, this means
-     * the user will be automatically be logged in.
+     * Saves the {@link org.apache.wiki.auth.user.UserProfile} for the user in a wiki session. This method verifies that a user profile to
+     * be saved doesn't collide with existing profiles; that is, the login name or full name is already used by another profile. If the
+     * profile collides, a <code>DuplicateUserException</code> is thrown. After saving the profile, the user database changes are committed,
+     * and the user's credential set is refreshed; if custom authentication is used, this means the user will be automatically be logged in.
      * </p>
      * <p>
-     * When the user's profile is saved successfully, this method fires a
-     * {@link WikiSecurityEvent#PROFILE_SAVE} event with the WikiSession as the
-     * source and the UserProfile as target. For existing profiles, if the
-     * user's full name changes, this method also fires a "name changed"
-     * event ({@link WikiSecurityEvent#PROFILE_NAME_CHANGED}) with the
-     * WikiSession as the source and an array containing the old and new
-     * UserProfiles, respectively. The <code>NAME_CHANGED</code> event allows
-     * the GroupManager and PageManager can change group memberships and
-     * ACLs if needed.
+     * When the user's profile is saved successfully, this method fires a {@link WikiSecurityEvent#PROFILE_SAVE} event with the WikiSession
+     * as the source and the UserProfile as target. For existing profiles, if the user's full name changes, this method also fires a
+     * "name changed" event ({@link WikiSecurityEvent#PROFILE_NAME_CHANGED}) with the WikiSession as the source and an array containing
+     * the old and new UserProfiles, respectively. The <code>NAME_CHANGED</code> event allows the GroupManager and PageManager can change
+     * group memberships and ACLs if needed.
      * </p>
      * <p>
-     * Note that WikiSessions normally attach event listeners to the
-     * UserManager, so changes to the profile will automatically cause the
+     * Note that WikiSessions normally attach event listeners to the UserManager, so changes to the profile will automatically cause the
      * correct Principals to be reloaded into the current WikiSession's Subject.
      * </p>
+     *
      * @param session the wiki session, which may not be <code>null</code>
      * @param profile the user profile, which may not be <code>null</code>
      * @throws DuplicateUserException if the proposed profile's login name or full name collides with another
@@ -247,303 +108,42 @@ public class UserManager {
      * {@link org.apache.wiki.workflow.DecisionRequiredException}. All other WikiException
      * indicate a condition that is not normal is probably due to mis-configuration
      */
-    public void setUserProfile( WikiSession session, UserProfile profile ) throws DuplicateUserException, WikiException
-    {
-        // Verify user is allowed to save profile!
-        final Permission p = new WikiPermission( m_engine.getApplicationName(), WikiPermission.EDIT_PROFILE_ACTION );
-        if ( !m_engine.getAuthorizationManager().checkPermission( session, p ) )
-        {
-            throw new WikiSecurityException( "You are not allowed to save wiki profiles." );
-        }
+    void setUserProfile( WikiSession session, UserProfile profile ) throws DuplicateUserException, WikiException;
 
-        // Check if profile is new, and see if container allows creation
-        final boolean newProfile = profile.isNew();
-
-        // Check if another user profile already has the fullname or loginname
-        final UserProfile oldProfile = getUserProfile( session );
-        final boolean nameChanged = ( oldProfile != null && oldProfile.getFullname() != null ) &&
-                                    !( oldProfile.getFullname().equals( profile.getFullname() ) &&
-                                    oldProfile.getLoginName().equals( profile.getLoginName() ) );
-        UserProfile otherProfile;
-        try
-        {
-            otherProfile = getUserDatabase().findByLoginName( profile.getLoginName() );
-            if ( otherProfile != null && !otherProfile.equals( oldProfile ) )
-            {
-                throw new DuplicateUserException( "security.error.login.taken", profile.getLoginName() );
-            }
-        }
-        catch( final NoSuchPrincipalException e )
-        {
-        }
-        try
-        {
-            otherProfile = getUserDatabase().findByFullName( profile.getFullname() );
-            if ( otherProfile != null && !otherProfile.equals( oldProfile ) )
-            {
-                throw new DuplicateUserException( "security.error.fullname.taken", profile.getFullname() );
-            }
-        }
-        catch( final NoSuchPrincipalException e )
-        {
-        }
-
-        // For new accounts, create approval workflow for user profile save.
-        if ( newProfile && oldProfile != null && oldProfile.isNew() )
-        {
-            startUserProfileCreationWorkflow(session, profile);
-
-            // If the profile doesn't need approval, then just log the user in
-
-            try
-            {
-                final AuthenticationManager mgr = m_engine.getAuthenticationManager();
-                if ( newProfile && !mgr.isContainerAuthenticated() )
-                {
-                    mgr.login( session, null, profile.getLoginName(), profile.getPassword() );
-                }
-            }
-            catch ( final WikiException e )
-            {
-                throw new WikiSecurityException( e.getMessage(), e );
-            }
-
-            // Alert all listeners that the profile changed...
-            // ...this will cause credentials to be reloaded in the wiki session
-            fireEvent( WikiSecurityEvent.PROFILE_SAVE, session, profile );
-        }
-
-        // For existing accounts, just save the profile
-        else
-        {
-            // If login name changed, rename it first
-            if ( nameChanged && oldProfile != null && !oldProfile.getLoginName().equals( profile.getLoginName() ) )
-            {
-                getUserDatabase().rename( oldProfile.getLoginName(), profile.getLoginName() );
-            }
-
-            // Now, save the profile (userdatabase will take care of timestamps for us)
-            getUserDatabase().save( profile );
-
-            if ( nameChanged )
-            {
-                // Fire an event if the login name or full name changed
-                final UserProfile[] profiles = new UserProfile[] { oldProfile, profile };
-                fireEvent( WikiSecurityEvent.PROFILE_NAME_CHANGED, session, profiles );
-            }
-            else
-            {
-                // Fire an event that says we have new a new profile (new principals)
-                fireEvent( WikiSecurityEvent.PROFILE_SAVE, session, profile );
-            }
-        }
-    }
-
-    public void startUserProfileCreationWorkflow( final WikiSession session, final UserProfile profile ) throws WikiException {
-        final WorkflowBuilder builder = WorkflowBuilder.getBuilder( m_engine );
-        final Principal submitter = session.getUserPrincipal();
-        final Step completionTask = m_engine.getTasksManager().buildSaveUserProfileTask( m_engine, session.getLocale() );
-
-        // Add user profile attribute as Facts for the approver (if required)
-        final boolean hasEmail = profile.getEmail() != null;
-        final Fact[] facts = new Fact[ hasEmail ? 4 : 3];
-        facts[0] = new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_PREFS_FULL_NAME, profile.getFullname() );
-        facts[1] = new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_PREFS_LOGIN_NAME, profile.getLoginName() );
-        facts[2] = new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_SUBMITTER, submitter.getName() );
-        if ( hasEmail )
-        {
-            facts[3] = new Fact( WorkflowManager.WF_UP_CREATE_SAVE_FACT_PREFS_EMAIL, profile.getEmail() );
-        }
-        final Workflow workflow = builder.buildApprovalWorkflow( submitter, 
-                                                                 WorkflowManager.WF_UP_CREATE_SAVE_APPROVER, 
-                                                                 null, 
-                                                                 WorkflowManager.WF_UP_CREATE_SAVE_DECISION_MESSAGE_KEY, 
-                                                                 facts, 
-                                                                 completionTask, 
-                                                                 null );
-
-        workflow.setAttribute( WorkflowManager.WF_UP_CREATE_SAVE_ATTR_SAVED_PROFILE, profile );
-        m_engine.getWorkflowManager().start(workflow);
-
-        final boolean approvalRequired = workflow.getCurrentStep() instanceof Decision;
-
-        // If the profile requires approval, redirect user to message page
-        if ( approvalRequired )
-        {
-            throw new DecisionRequiredException( "This profile must be approved before it becomes active" );
-        }
-    }
+    void startUserProfileCreationWorkflow( WikiSession session, UserProfile profile ) throws WikiException;
 
     /**
-     * <p> Extracts user profile parameters from the HTTP request and populates
-     * a UserProfile with them. The UserProfile will either be a copy of the
-     * user's existing profile (if one can be found), or a new profile (if not).
-     * The rules for populating the profile as as follows: </p> <ul> <li>If the
-     * <code>email</code> or <code>password</code> parameter values differ
-     * from those in the existing profile, the passed parameters override the
-     * old values.</li> <li>For new profiles, the user-supplied
-     * <code>fullname</code> parameter is always
-     * used; for existing profiles the existing value is used, and whatever
-     * value the user supplied is discarded. The wiki name is automatically
-     * computed by taking the full name and extracting all whitespace.</li>
-     * <li>In all cases, the
-     * created/last modified timestamps of the user's existing or new profile
-     * always override whatever values the user supplied.</li> <li>If
-     * container authentication is used, the login name property of the profile
-     * is set to the name of
-     * {@link org.apache.wiki.WikiSession#getLoginPrincipal()}. Otherwise,
-     * the value of the <code>loginname</code> parameter is used.</li> </ul>
+     * <p> Extracts user profile parameters from the HTTP request and populates a UserProfile with them. The UserProfile will either be a
+     * copy of the user's existing profile (if one can be found), or a new profile (if not). The rules for populating the profile as as
+     * follows: </p>
+     * <ul>
+     * <li>If the <code>email</code> or <code>password</code> parameter values differ from those in the existing profile, the passed
+     * parameters override the old values.</li>
+     * <li>For new profiles, the user-supplied <code>fullname</code> parameter is always used; for existing profiles the existing value is
+     * used, and whatever value the user supplied is discarded. The wiki name is automatically computed by taking the full name and
+     * extracting all whitespace.</li>
+     * <li>In all cases, the created/last modified timestamps of the user's existing or new profile always override whatever values the user
+     * supplied.</li>
+     * <li>If container authentication is used, the login name property of the profile is set to the name of
+     * {@link org.apache.wiki.WikiSession#getLoginPrincipal()}. Otherwise, the value of the <code>loginname</code> parameter is used.</li>
+     * </ul>
+     *
      * @param context the current wiki context
      * @return a new, populated user profile
      */
-    public UserProfile parseProfile( WikiContext context )
-    {
-        // Retrieve the user's profile (may have been previously cached)
-        final UserProfile profile = getUserProfile( context.getWikiSession() );
-        final HttpServletRequest request = context.getHttpRequest();
-
-        // Extract values from request stream (cleanse whitespace as needed)
-        String loginName = request.getParameter( PARAM_LOGINNAME );
-        String password = request.getParameter( PARAM_PASSWORD );
-        String fullname = request.getParameter( PARAM_FULLNAME );
-        String email = request.getParameter( PARAM_EMAIL );
-        loginName = InputValidator.isBlank( loginName ) ? null : loginName;
-        password = InputValidator.isBlank( password ) ? null : password;
-        fullname = InputValidator.isBlank( fullname ) ? null : fullname;
-        email = InputValidator.isBlank( email ) ? null : email;
-
-        // A special case if we have container authentication
-        // If authenticated, login name is always taken from container
-        if ( m_engine.getAuthenticationManager().isContainerAuthenticated() &&
-                context.getWikiSession().isAuthenticated() )
-        {
-            loginName = context.getWikiSession().getLoginPrincipal().getName();
-        }
-
-        // Set the profile fields!
-        profile.setLoginName( loginName );
-        profile.setEmail( email );
-        profile.setFullname( fullname );
-        profile.setPassword( password );
-        return profile;
-    }
+    UserProfile parseProfile( WikiContext context );
 
     /**
-     * Validates a user profile, and appends any errors to the session errors
-     * list. If the profile is new, the password will be checked to make sure it
-     * isn't null. Otherwise, the password is checked for length and that it
-     * matches the value of the 'password2' HTTP parameter. Note that we have a
-     * special case when container-managed authentication is used and the user
-     * is not authenticated; this will always cause validation to fail. Any
-     * validation errors are added to the wiki session's messages collection
+     * Validates a user profile, and appends any errors to the session errors list. If the profile is new, the password will be checked to
+     * make sure it isn't null. Otherwise, the password is checked for length and that it matches the value of the 'password2' HTTP
+     * parameter. Note that we have a special case when container-managed authentication is used and the user is not authenticated;
+     * this will always cause validation to fail. Any validation errors are added to the wiki session's messages collection
      * (see {@link WikiSession#getMessages()}.
+     *
      * @param context the current wiki context
      * @param profile the supplied UserProfile
      */
-    public void validateProfile( WikiContext context, UserProfile profile )
-    {
-        final boolean isNew = profile.isNew();
-        final WikiSession session = context.getWikiSession();
-        final InputValidator validator = new InputValidator( SESSION_MESSAGES, context );
-        final ResourceBundle rb = Preferences.getBundle( context, InternationalizationManager.CORE_BUNDLE );
-
-        //
-        //  Query the SpamFilter first
-        //
-        final FilterManager fm = m_engine.getFilterManager();
-        final List<PageFilter> ls = fm.getFilterList();
-        for( final PageFilter pf : ls )
-        {
-            if( pf instanceof SpamFilter )
-            {
-                if( ((SpamFilter)pf).isValidUserProfile( context, profile ) == false )
-                {
-                    session.addMessage( SESSION_MESSAGES, "Invalid userprofile" );
-                    return;
-                }
-                break;
-            }
-        }
-
-        // If container-managed auth and user not logged in, throw an error
-        if ( m_engine.getAuthenticationManager().isContainerAuthenticated()
-             && !context.getWikiSession().isAuthenticated() )
-        {
-            session.addMessage( SESSION_MESSAGES, rb.getString("security.error.createprofilebeforelogin") );
-        }
-
-        validator.validateNotNull( profile.getLoginName(), rb.getString("security.user.loginname") );
-        validator.validateNotNull( profile.getFullname(), rb.getString("security.user.fullname") );
-        validator.validate( profile.getEmail(), rb.getString("security.user.email"), InputValidator.EMAIL );
-
-        // If new profile, passwords must match and can't be null
-        if ( !m_engine.getAuthenticationManager().isContainerAuthenticated() )
-        {
-            final String password = profile.getPassword();
-            if ( password == null )
-            {
-                if ( isNew )
-                {
-                    session.addMessage( SESSION_MESSAGES, rb.getString("security.error.blankpassword") );
-                }
-            }
-            else
-            {
-                final HttpServletRequest request = context.getHttpRequest();
-                final String password2 = ( request == null ) ? null : request.getParameter( "password2" );
-                if ( !password.equals( password2 ) )
-                {
-                    session.addMessage( SESSION_MESSAGES, rb.getString("security.error.passwordnomatch") );
-                }
-            }
-        }
-
-        UserProfile otherProfile;
-        final String fullName = profile.getFullname();
-        final String loginName = profile.getLoginName();
-        final String email = profile.getEmail();
-
-        // It's illegal to use as a full name someone else's login name
-        try
-        {
-            otherProfile = getUserDatabase().find( fullName );
-            if ( otherProfile != null && !profile.equals( otherProfile ) && !fullName.equals( otherProfile.getFullname() ) )
-            {
-                final Object[] args = { fullName };
-                session.addMessage( SESSION_MESSAGES, MessageFormat.format( rb.getString("security.error.illegalfullname"), args ) );
-            }
-        }
-        catch ( final NoSuchPrincipalException e)
-        { /* It's clean */ }
-
-        // It's illegal to use as a login name someone else's full name
-        try
-        {
-            otherProfile = getUserDatabase().find( loginName );
-            if ( otherProfile != null && !profile.equals( otherProfile ) && !loginName.equals( otherProfile.getLoginName() ) )
-            {
-                final Object[] args = { loginName };
-                session.addMessage( SESSION_MESSAGES, MessageFormat.format( rb.getString("security.error.illegalloginname"), args ) );
-            }
-        }
-        catch ( final NoSuchPrincipalException e)
-        { /* It's clean */ }
-
-        // It's illegal to use multiple accounts with the same email
-        try
-        {
-            otherProfile = getUserDatabase().findByEmail( email );
-            if ( otherProfile != null
-                && !profile.getUid().equals(otherProfile.getUid()) // Issue JSPWIKI-1042
-                && !profile.equals( otherProfile ) && StringUtils.lowerCase( email ).equals( StringUtils.lowerCase(otherProfile.getEmail() ) ) )
-            {
-                final Object[] args = { email };
-                session.addMessage( SESSION_MESSAGES, MessageFormat.format( rb.getString("security.error.email.taken"), args ) );
-            }
-        }
-        catch ( final NoSuchPrincipalException e)
-        { /* It's clean */ }
-    }
+    void validateProfile( WikiContext context, UserProfile profile );
 
     /**
      *  A helper method for returning all of the known WikiNames in this system.
@@ -551,215 +151,35 @@ public class UserManager {
      *  @return An Array of Principals
      *  @throws WikiSecurityException If for reason the names cannot be fetched
      */
-    public Principal[] listWikiNames()
-        throws WikiSecurityException
-    {
-        return getUserDatabase().getWikiNames();
-    }
-
-    /**
-     * This is a database that gets used if nothing else is available. It does nothing of note - it just mostly throws
-     * NoSuchPrincipalExceptions if someone tries to log in.
-     */
-    public static class DummyUserDatabase extends AbstractUserDatabase {
-
-        /**
-         * No-op.
-         * @param loginName the login name to delete
-         */
-        @Override
-        public void deleteByLoginName( String loginName ) {
-            // No operation
-        }
-
-        /**
-         * No-op; always throws <code>NoSuchPrincipalException</code>.
-         * @param index the name to search for
-         * @return the user profile
-         * @throws NoSuchPrincipalException always...
-         */
-        @Override
-        public UserProfile findByEmail(final String index) throws NoSuchPrincipalException {
-            throw new NoSuchPrincipalException("No user profiles available");
-        }
-
-        /**
-         * No-op; always throws <code>NoSuchPrincipalException</code>.
-         * @param index the name to search for
-         * @return the user profile
-         * @throws NoSuchPrincipalException always...
-         */
-        @Override
-        public UserProfile findByFullName(final String index) throws NoSuchPrincipalException {
-            throw new NoSuchPrincipalException("No user profiles available");
-        }
-
-        /**
-         * No-op; always throws <code>NoSuchPrincipalException</code>.
-         * @param index the name to search for
-         * @return the user profile
-         * @throws NoSuchPrincipalException always...
-         */
-        @Override
-        public UserProfile findByLoginName(final String index) throws NoSuchPrincipalException {
-            throw new NoSuchPrincipalException("No user profiles available");
-        }
-
-        /**
-         * No-op; always throws <code>NoSuchPrincipalException</code>.
-         * @param uid the unique identifier to search for
-         * @return the user profile
-         * @throws NoSuchPrincipalException always...
-         */
-        @Override
-        public UserProfile findByUid( final String uid ) throws NoSuchPrincipalException {
-            throw new NoSuchPrincipalException("No user profiles available");
-        }
-        /**
-         * No-op; always throws <code>NoSuchPrincipalException</code>.
-         * @param index the name to search for
-         * @return the user profile
-         * @throws NoSuchPrincipalException always...
-         */
-        @Override
-        public UserProfile findByWikiName(final String index) throws NoSuchPrincipalException {
-            throw new NoSuchPrincipalException("No user profiles available");
-        }
-
-        /**
-         * No-op.
-         * @return a zero-length array
-         */
-        @Override
-        public Principal[] getWikiNames() {
-            return new Principal[0];
-        }
-
-        /**
-         * No-op.
-         *
-         * @param engine the wiki engine
-         * @param props the properties used to initialize the wiki engine
-         */
-        @Override
-        public void initialize(final WikiEngine engine, final Properties props) {
-        }
-
-        /**
-         * No-op; always throws <code>NoSuchPrincipalException</code>.
-         * @param loginName the login name
-         * @param newName the proposed new login name
-         * @throws NoSuchPrincipalException always...
-         */
-        @Override
-        public void rename( final String loginName, final String newName ) throws NoSuchPrincipalException {
-            throw new NoSuchPrincipalException("No user profiles available");
-        }
-
-        /**
-         * No-op.
-         * @param profile the user profile
-         */
-        @Override
-        public void save( final UserProfile profile ) {
-        }
-
-    }
+    Principal[] listWikiNames() throws WikiSecurityException;
 
     // events processing .......................................................
 
     /**
-     * Registers a WikiEventListener with this instance.
-     * This is a convenience method.
+     * Registers a WikiEventListener with this instance. This is a convenience method.
+     *
      * @param listener the event listener
      */
-    public synchronized void addWikiEventListener( WikiEventListener listener )
-    {
-        WikiEventManager.addWikiEventListener( this, listener );
-    }
+    void addWikiEventListener( WikiEventListener listener );
 
     /**
-     * Un-registers a WikiEventListener with this instance.
-     * This is a convenience method.
+     * Un-registers a WikiEventListener with this instance. This is a convenience method.
+     *
      * @param listener the event listener
      */
-    public synchronized void removeWikiEventListener( WikiEventListener listener )
-    {
-        WikiEventManager.removeWikiEventListener( this, listener );
-    }
+    void removeWikiEventListener( WikiEventListener listener );
 
     /**
-     *  Fires a WikiSecurityEvent of the provided type, Principal and target Object
-     *  to all registered listeners.
+     *  Fires a WikiSecurityEvent of the provided type, Principal and target Object to all registered listeners.
      *
      * @see org.apache.wiki.event.WikiSecurityEvent
      * @param type       the event type to be fired
      * @param session    the wiki session supporting the event
      * @param profile    the user profile (or array of user profiles), which may be <code>null</code>
      */
-    protected void fireEvent( int type, WikiSession session, Object profile )
-    {
-        if ( WikiEventManager.isListening(this) )
-        {
-            WikiEventManager.fireEvent(this,new WikiSecurityEvent(session,type,profile));
-        }
-    }
-
-    /**
-     *  Implements the JSON API for usermanager.
-     *  <p>
-     *  Even though this gets serialized whenever container shuts down/restarts, this gets reinstalled to the session when JSPWiki starts.
-     *  This means that it's not actually necessary to save anything.
-     */
-    public static final class JSONUserModule implements WikiAjaxServlet {
-
-		private volatile UserManager m_manager;
-
-        /**
-         *  Create a new JSONUserModule.
-         *  @param mgr Manager
-         */
-        public JSONUserModule( UserManager mgr )
-        {
-            m_manager = mgr;
-        }
-
-        @Override
-        public String getServletMapping() {
-        	return JSON_USERS;
-        }
-
-        @Override
-        public void service(HttpServletRequest req, HttpServletResponse resp, String actionName, List<String> params) throws ServletException, IOException {
-        	try {
-            	if( params.size() < 1 ) {
-            		return;
-            	}
-        		final String uid = params.get(0);
-	        	log.debug("uid="+uid);
-	        	if (StringUtils.isNotBlank(uid)) {
-		            final UserProfile prof = getUserInfo(uid);
-		            resp.getWriter().write(AjaxUtil.toJson(prof));
-	        	}
-        	} catch (final NoSuchPrincipalException e) {
-        		throw new ServletException(e);
-        	}
-        }
-
-        /**
-         *  Directly returns the UserProfile object attached to an uid.
-         *
-         *  @param uid The user id (e.g. WikiName)
-         *  @return A UserProfile object
-         *  @throws NoSuchPrincipalException If such a name does not exist.
-         */
-        public UserProfile getUserInfo( String uid ) throws NoSuchPrincipalException {
-            if( m_manager != null ) {
-                final UserProfile prof = m_manager.getUserDatabase().find( uid );
-                return prof;
-            }
-
-            throw new IllegalStateException("The manager is offline.");
+    default void fireEvent( final int type, final WikiSession session, final Object profile ) {
+        if( WikiEventManager.isListening( this ) ) {
+            WikiEventManager.fireEvent( this, new WikiSecurityEvent( session, type, profile ) );
         }
     }
 
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/user/AbstractUserDatabase.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/user/AbstractUserDatabase.java
index df51c10..78b030d 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/auth/user/AbstractUserDatabase.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/user/AbstractUserDatabase.java
@@ -20,7 +20,7 @@ package org.apache.wiki.auth.user;
 
 import org.apache.commons.lang3.math.NumberUtils;
 import org.apache.log4j.Logger;
-import org.apache.wiki.WikiEngine;
+import org.apache.wiki.api.core.Engine;
 import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
 import org.apache.wiki.auth.NoSuchPrincipalException;
 import org.apache.wiki.auth.WikiPrincipal;
@@ -37,12 +37,11 @@ import java.util.Properties;
 import java.util.UUID;
 
 /**
- * Abstract UserDatabase class that provides convenience methods for finding
- * profiles, building Principal collections and hashing passwords.
+ * Abstract UserDatabase class that provides convenience methods for finding profiles, building Principal collections and hashing passwords.
+ *
  * @since 2.3
  */
-public abstract class AbstractUserDatabase implements UserDatabase
-{
+public abstract class AbstractUserDatabase implements UserDatabase {
 
     protected static final Logger log = Logger.getLogger( AbstractUserDatabase.class );
     protected static final String SHA_PREFIX = "{SHA}";
@@ -57,46 +56,33 @@ public abstract class AbstractUserDatabase implements UserDatabase
      * @param index the login name, full name, or wiki name
      * @see org.apache.wiki.auth.user.UserDatabase#find(java.lang.String)
      */
-    public UserProfile find( String index ) throws NoSuchPrincipalException
-    {
+    @Override public UserProfile find( final String index ) throws NoSuchPrincipalException {
         UserProfile profile = null;
 
         // Try finding by full name
-        try
-        {
+        try {
             profile = findByFullName( index );
+        } catch( final NoSuchPrincipalException e ) {
         }
-        catch ( NoSuchPrincipalException e )
-        {
-        }
-        if ( profile != null )
-        {
+        if( profile != null ) {
             return profile;
         }
 
         // Try finding by wiki name
-        try
-        {
+        try {
             profile = findByWikiName( index );
+        } catch( final NoSuchPrincipalException e ) {
         }
-        catch ( NoSuchPrincipalException e )
-        {
-        }
-        if ( profile != null )
-        {
+        if( profile != null ) {
             return profile;
         }
 
         // Try finding by login name
-        try
-        {
+        try {
             profile = findByLoginName( index );
+        } catch( final NoSuchPrincipalException e ) {
         }
-        catch ( NoSuchPrincipalException e )
-        {
-        }
-        if ( profile != null )
-        {
+        if( profile != null ) {
             return profile;
         }
 
@@ -107,25 +93,25 @@ public abstract class AbstractUserDatabase implements UserDatabase
      * {@inheritDoc}
      * @see org.apache.wiki.auth.user.UserDatabase#findByEmail(java.lang.String)
      */
-    public abstract UserProfile findByEmail( String index ) throws NoSuchPrincipalException;
+    @Override public abstract UserProfile findByEmail( String index ) throws NoSuchPrincipalException;
 
     /**
      * {@inheritDoc}
      * @see org.apache.wiki.auth.user.UserDatabase#findByFullName(java.lang.String)
      */
-    public abstract UserProfile findByFullName( String index ) throws NoSuchPrincipalException;
+    @Override public abstract UserProfile findByFullName( String index ) throws NoSuchPrincipalException;
 
     /**
      * {@inheritDoc}
      * @see org.apache.wiki.auth.user.UserDatabase#findByLoginName(java.lang.String)
      */
-    public abstract UserProfile findByLoginName( String index ) throws NoSuchPrincipalException;
+    @Override public abstract UserProfile findByLoginName( String index ) throws NoSuchPrincipalException;
 
     /**
      * {@inheritDoc}
      * @see org.apache.wiki.auth.user.UserDatabase#findByWikiName(java.lang.String)
      */
-    public abstract UserProfile findByWikiName( String index ) throws NoSuchPrincipalException;
+    @Override public abstract UserProfile findByWikiName( String index ) throws NoSuchPrincipalException;
 
     /**
      * <p>Looks up the Principals representing a user from the user database. These
@@ -142,38 +128,31 @@ public abstract class AbstractUserDatabase implements UserDatabase
      * @see org.apache.wiki.auth.user.UserDatabase#getPrincipals(java.lang.String)
      * @throws NoSuchPrincipalException {@inheritDoc}
      */
-    public Principal[] getPrincipals( String identifier ) throws NoSuchPrincipalException
+    @Override public Principal[] getPrincipals( final String identifier ) throws NoSuchPrincipalException
     {
-        try
-        {
-            UserProfile profile = findByLoginName( identifier );
-            ArrayList<Principal> principals = new ArrayList<Principal>();
-            if ( profile.getLoginName() != null && profile.getLoginName().length() > 0 )
-            {
+        try {
+            final UserProfile profile = findByLoginName( identifier );
+            final ArrayList< Principal > principals = new ArrayList<>();
+            if( profile.getLoginName() != null && profile.getLoginName().length() > 0 ) {
                 principals.add( new WikiPrincipal( profile.getLoginName(), WikiPrincipal.LOGIN_NAME ) );
             }
-            if ( profile.getFullname() != null && profile.getFullname().length() > 0 )
-            {
+            if( profile.getFullname() != null && profile.getFullname().length() > 0 ) {
                 principals.add( new WikiPrincipal( profile.getFullname(), WikiPrincipal.FULL_NAME ) );
             }
-            if ( profile.getWikiName() != null && profile.getWikiName().length() > 0 )
-            {
+            if( profile.getWikiName() != null && profile.getWikiName().length() > 0 ) {
                 principals.add( new WikiPrincipal( profile.getWikiName(), WikiPrincipal.WIKI_NAME ) );
             }
-            return principals.toArray( new Principal[principals.size()] );
-        }
-        catch( NoSuchPrincipalException e )
-        {
+            return principals.toArray( new Principal[ principals.size() ] );
+        } catch( final NoSuchPrincipalException e ) {
             throw e;
         }
     }
 
     /**
      * {@inheritDoc}
-     * @see org.apache.wiki.auth.user.UserDatabase#initialize(org.apache.wiki.WikiEngine, java.util.Properties)
+     * @see org.apache.wiki.auth.user.UserDatabase#initialize(org.apache.wiki.api.core.Engine, java.util.Properties)
      */
-    public abstract void initialize( WikiEngine engine, Properties props ) throws NoRequiredPropertyException,
-            WikiSecurityException;
+    @Override public abstract void initialize( Engine engine, Properties props ) throws NoRequiredPropertyException, WikiSecurityException;
 
     /**
      * Factory method that instantiates a new DefaultUserProfile with a new, distinct
@@ -181,7 +160,7 @@ public abstract class AbstractUserDatabase implements UserDatabase
      * 
      * @return A new, empty profile.
      */
-    public UserProfile newProfile()
+    @Override public UserProfile newProfile()
     {
         return DefaultUserProfile.newProfile( this );
     }
@@ -190,66 +169,51 @@ public abstract class AbstractUserDatabase implements UserDatabase
      * {@inheritDoc}
      * @see org.apache.wiki.auth.user.UserDatabase#save(org.apache.wiki.auth.user.UserProfile)
      */
-    public abstract void save( UserProfile profile ) throws WikiSecurityException;
+    @Override public abstract void save( UserProfile profile ) throws WikiSecurityException;
 
     /**
-     * Validates the password for a given user. If the user does not exist in
-     * the user database, this method always returns <code>false</code>. If
-     * the user exists, the supplied password is compared to the stored
-     * password. Note that if the stored password's value starts with
-     * <code>{SHA}</code>, the supplied password is hashed prior to the
-     * comparison.
+     * Validates the password for a given user. If the user does not exist in the user database, this method always returns
+     * <code>false</code>. If the user exists, the supplied password is compared to the stored password. Note that if the stored password's
+     * value starts with <code>{SHA}</code>, the supplied password is hashed prior to the comparison.
+     *
      * @param loginName the user's login name
      * @param password the user's password (obtained from user input, e.g., a web form)
-     * @return <code>true</code> if the supplied user password matches the
-     * stored password
-     * @see org.apache.wiki.auth.user.UserDatabase#validatePassword(java.lang.String,
-     *      java.lang.String)
+     * @return <code>true</code> if the supplied user password matches the stored password
+     * @see org.apache.wiki.auth.user.UserDatabase#validatePassword(java.lang.String, java.lang.String)
      */
-    public boolean validatePassword( String loginName, String password )
-    {
-        String hashedPassword;
-        try
-        {
-            UserProfile profile = findByLoginName( loginName );
+    @Override public boolean validatePassword( final String loginName, final String password ) {
+        final String hashedPassword;
+        try {
+            final UserProfile profile = findByLoginName( loginName );
             String storedPassword = profile.getPassword();
-            
+
             // Is the password stored as a salted hash (the new 2.8 format?)
-            boolean newPasswordFormat = storedPassword.startsWith( SSHA_PREFIX );
-            
+            final boolean newPasswordFormat = storedPassword.startsWith( SSHA_PREFIX );
+
             // If new format, verify the hash
-            if ( newPasswordFormat )
-            {
+            if( newPasswordFormat ) {
                 hashedPassword = getHash( password );
                 return CryptoUtil.verifySaltedPassword( password.getBytes( StandardCharsets.UTF_8 ), storedPassword );
             }
 
             // If old format, verify using the old SHA verification algorithm
-            if ( storedPassword.startsWith( SHA_PREFIX ) )
-            {
+            if( storedPassword.startsWith( SHA_PREFIX ) ) {
                 storedPassword = storedPassword.substring( SHA_PREFIX.length() );
             }
             hashedPassword = getOldHash( password );
-            boolean verified = hashedPassword.equals( storedPassword ); 
-            
+            final boolean verified = hashedPassword.equals( storedPassword );
+
             // If in the old format and password verified, upgrade the hash to SSHA
-            if ( verified )
-            {
+            if( verified ) {
                 profile.setPassword( password );
                 save( profile );
             }
-            
+
             return verified;
-        }
-        catch( NoSuchPrincipalException e )
-        {
-        }
-        catch( NoSuchAlgorithmException e )
-        {
+        } catch( final NoSuchPrincipalException e ) {
+        } catch( final NoSuchAlgorithmException e ) {
             log.error( "Unsupported algorithm: " + e.getMessage() );
-        }
-        catch( WikiSecurityException e )
-        {
+        } catch( final WikiSecurityException e ) {
             log.error( "Could not upgrade SHA password to SSHA because profile could not be saved. Reason: " + e.getMessage(), e );
         }
         return false;
@@ -261,21 +225,17 @@ public abstract class AbstractUserDatabase implements UserDatabase
      * @param db The database for which the UID should be generated.
      * @return A random, unique UID.
      */
-    protected static String generateUid( UserDatabase db ) {
+    protected static String generateUid( final UserDatabase db ) {
         // Keep generating UUIDs until we find one that doesn't collide
-        String uid = null;
+        String uid;
         boolean collision;
         
-        do 
-        {
+        do {
             uid = UUID.randomUUID().toString();
             collision = true;
-            try
-            {
+            try {
                 db.findByUid( uid );
-            }
-            catch ( NoSuchPrincipalException e )
-            {
+            } catch ( final NoSuchPrincipalException e ) {
                 collision = false;
             }
         } 
@@ -284,9 +244,9 @@ public abstract class AbstractUserDatabase implements UserDatabase
     }
     
     /**
-     * Private method that calculates the salted SHA-1 hash of a given
-     * <code>String</code>. Note that as of JSPWiki 2.8, this method calculates
-     * a <em>salted</em> hash rather than a plain hash.
+     * Private method that calculates the salted SHA-1 hash of a given <code>String</code>. Note that as of JSPWiki 2.8, this method
+     * calculates a <em>salted</em> hash rather than a plain hash.
+     *
      * @param text the text to hash
      * @return the result hash
      */
@@ -300,8 +260,8 @@ public abstract class AbstractUserDatabase implements UserDatabase
     }
 
     /**
-     * Private method that calculates the SHA-1 hash of a given
-     * <code>String</code>
+     * Private method that calculates the SHA-1 hash of a given <code>String</code>
+     *
      * @param text the text to hash
      * @return the result hash
      * @deprecated this method is retained for backwards compatibility purposes; use {@link #getHash(String)} instead
@@ -310,7 +270,7 @@ public abstract class AbstractUserDatabase implements UserDatabase
         try {
             final MessageDigest md = MessageDigest.getInstance( "SHA" );
             md.update( text.getBytes( StandardCharsets.UTF_8 ) );
-            byte[] digestedBytes = md.digest();
+            final byte[] digestedBytes = md.digest();
             return ByteUtils.bytes2hex( digestedBytes );
         } catch( final NoSuchAlgorithmException e ) {
             log.error( "Error creating SHA password hash:" + e.getMessage() );
@@ -320,6 +280,7 @@ public abstract class AbstractUserDatabase implements UserDatabase
 
     /**
      * Parses a long integer from a supplied string, or returns 0 if not parsable.
+     *
      * @param value the string to parse
      * @return the value parsed
      */
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/user/JDBCUserDatabase.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/user/JDBCUserDatabase.java
index 53da585..93ca181 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/auth/user/JDBCUserDatabase.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/user/JDBCUserDatabase.java
@@ -19,7 +19,7 @@
 package org.apache.wiki.auth.user;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.wiki.WikiEngine;
+import org.apache.wiki.api.core.Engine;
 import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
 import org.apache.wiki.auth.NoSuchPrincipalException;
 import org.apache.wiki.auth.WikiPrincipal;
@@ -181,13 +181,7 @@ import java.util.Set;
  * </p>
  * <p>
  * JDBCUserDatabase commits changes as transactions if the back-end database
- * supports them. If the database supports transactions, user profile changes
- * are saved to permanent storage only when the {@link #commit()} method is
- * called. If the database does <em>not</em> support transactions, then
- * changes are made immediately (during the {@link #save(UserProfile)} method),
- * and the {@linkplain #commit()} method no-ops. Thus, callers should always
- * call the {@linkplain #commit()} method after saving a profile to guarantee
- * that changes are applied.
+ * supports them. Changes are made immediately (during the {@link #save(UserProfile)} method).
  * </p>
  * 
  * @since 2.3
@@ -316,13 +310,13 @@ public class JDBCUserDatabase extends AbstractUserDatabase {
      * @param loginName the login name of the user profile that shall be deleted
      */
     @Override
-    public void deleteByLoginName( String loginName ) throws NoSuchPrincipalException, WikiSecurityException {
+    public void deleteByLoginName( final String loginName ) throws NoSuchPrincipalException, WikiSecurityException {
         // Get the existing user; if not found, throws NoSuchPrincipalException
         findByLoginName( loginName );
 
-        try( Connection conn = m_ds.getConnection() ; 
-             PreparedStatement ps1 = conn.prepareStatement( m_deleteUserByLoginName ); 
-             PreparedStatement ps2 = conn.prepareStatement( m_deleteRoleByLoginName ) )
+        try( final Connection conn = m_ds.getConnection() ;
+             final PreparedStatement ps1 = conn.prepareStatement( m_deleteUserByLoginName );
+             final PreparedStatement ps2 = conn.prepareStatement( m_deleteRoleByLoginName ) )
         {
             // Open the database connection
             if( m_supportsCommits ) {
@@ -341,7 +335,7 @@ public class JDBCUserDatabase extends AbstractUserDatabase {
             if( m_supportsCommits ) {
                 conn.commit();
             }
-        } catch( SQLException e ) {
+        } catch( final SQLException e ) {
             throw new WikiSecurityException( e.getMessage(), e );
         }
     }
@@ -350,7 +344,7 @@ public class JDBCUserDatabase extends AbstractUserDatabase {
      * @see org.apache.wiki.auth.user.UserDatabase#findByEmail(java.lang.String)
      */
     @Override
-    public UserProfile findByEmail( String index ) throws NoSuchPrincipalException {
+    public UserProfile findByEmail( final String index ) throws NoSuchPrincipalException {
         return findByPreparedStatement( m_findByEmail, index );
     }
 
@@ -358,7 +352,7 @@ public class JDBCUserDatabase extends AbstractUserDatabase {
      * @see org.apache.wiki.auth.user.UserDatabase#findByFullName(java.lang.String)
      */
     @Override
-    public UserProfile findByFullName( String index ) throws NoSuchPrincipalException {
+    public UserProfile findByFullName( final String index ) throws NoSuchPrincipalException {
         return findByPreparedStatement( m_findByFullName, index );
     }
 
@@ -366,7 +360,7 @@ public class JDBCUserDatabase extends AbstractUserDatabase {
      * @see org.apache.wiki.auth.user.UserDatabase#findByLoginName(java.lang.String)
      */
     @Override
-    public UserProfile findByLoginName( String index ) throws NoSuchPrincipalException {
+    public UserProfile findByLoginName( final String index ) throws NoSuchPrincipalException {
         return findByPreparedStatement( m_findByLoginName, index );
     }
 
@@ -374,7 +368,7 @@ public class JDBCUserDatabase extends AbstractUserDatabase {
      * @see org.apache.wiki.auth.user.UserDatabase#findByWikiName(String)
      */
     @Override
-    public UserProfile findByUid( String uid ) throws NoSuchPrincipalException {
+    public UserProfile findByUid( final String uid ) throws NoSuchPrincipalException {
         return findByPreparedStatement( m_findByUid, uid );
     }
 
@@ -382,7 +376,7 @@ public class JDBCUserDatabase extends AbstractUserDatabase {
      * @see org.apache.wiki.auth.user.UserDatabase#findByWikiName(String)
      */
     @Override
-    public UserProfile findByWikiName( String index ) throws NoSuchPrincipalException {
+    public UserProfile findByWikiName( final String index ) throws NoSuchPrincipalException {
         return findByPreparedStatement( m_findByWikiName, index );
     }
 
@@ -395,21 +389,21 @@ public class JDBCUserDatabase extends AbstractUserDatabase {
      */
     @Override
     public Principal[] getWikiNames() throws WikiSecurityException {
-        Set<Principal> principals = new HashSet<>();
-        try( Connection conn = m_ds.getConnection();
-             PreparedStatement ps = conn.prepareStatement( m_findAll );
-             ResultSet rs = ps.executeQuery() )
+        final Set<Principal> principals = new HashSet<>();
+        try( final Connection conn = m_ds.getConnection();
+             final PreparedStatement ps = conn.prepareStatement( m_findAll );
+             final ResultSet rs = ps.executeQuery() )
         {
             while ( rs.next() ) {
-                String wikiName = rs.getString( m_wikiName );
+                final String wikiName = rs.getString( m_wikiName );
                 if( wikiName == null ) {
                     log.warn( "Detected null wiki name in XMLUserDataBase. Check your user database." );
                 } else {
-                    Principal principal = new WikiPrincipal( wikiName, WikiPrincipal.WIKI_NAME );
+                    final Principal principal = new WikiPrincipal( wikiName, WikiPrincipal.WIKI_NAME );
                     principals.add( principal );
                 }
             }
-        } catch( SQLException e ) {
+        } catch( final SQLException e ) {
             throw new WikiSecurityException( e.getMessage(), e );
         }
 
@@ -417,19 +411,18 @@ public class JDBCUserDatabase extends AbstractUserDatabase {
     }
 
     /**
-     * @see org.apache.wiki.auth.user.UserDatabase#initialize(org.apache.wiki.WikiEngine,
-     *      java.util.Properties)
+     * @see org.apache.wiki.auth.user.UserDatabase#initialize(org.apache.wiki.api.core.Engine, java.util.Properties)
      */
     @Override
-    public void initialize( WikiEngine engine, Properties props ) throws NoRequiredPropertyException, WikiSecurityException {
-        String jndiName = props.getProperty( PROP_DB_DATASOURCE, DEFAULT_DB_JNDI_NAME );
+    public void initialize( final Engine engine, final Properties props ) throws NoRequiredPropertyException, WikiSecurityException {
+        final String jndiName = props.getProperty( PROP_DB_DATASOURCE, DEFAULT_DB_JNDI_NAME );
         try {
-            Context initCtx = new InitialContext();
-            Context ctx = (Context) initCtx.lookup( "java:comp/env" );
+            final Context initCtx = new InitialContext();
+            final Context ctx = (Context) initCtx.lookup( "java:comp/env" );
             m_ds = (DataSource) ctx.lookup( jndiName );
 
             // Prepare the SQL selectors
-            String userTable = props.getProperty( PROP_DB_TABLE, DEFAULT_DB_TABLE );
+            final String userTable = props.getProperty( PROP_DB_TABLE, DEFAULT_DB_TABLE );
             m_email = props.getProperty( PROP_DB_EMAIL, DEFAULT_DB_EMAIL );
             m_fullName = props.getProperty( PROP_DB_FULL_NAME, DEFAULT_DB_FULL_NAME );
             m_lockExpiry = props.getProperty( PROP_DB_LOCK_EXPIRY, DEFAULT_DB_LOCK_EXPIRY );
@@ -475,8 +468,8 @@ public class JDBCUserDatabase extends AbstractUserDatabase {
                               + "WHERE " + m_loginName + "=?";
 
             // Prepare the role insert SQL
-            String roleTable = props.getProperty( PROP_DB_ROLE_TABLE, DEFAULT_DB_ROLE_TABLE );
-            String role = props.getProperty( PROP_DB_ROLE, DEFAULT_DB_ROLE );
+            final String roleTable = props.getProperty( PROP_DB_ROLE_TABLE, DEFAULT_DB_ROLE_TABLE );
+            final String role = props.getProperty( PROP_DB_ROLE, DEFAULT_DB_ROLE );
             m_insertRole = "INSERT INTO " + roleTable + " (" + m_loginName + "," + role + ") VALUES (?,?)";
             m_findRoles = "SELECT * FROM " + roleTable + " WHERE " + m_loginName + "=?";
 
@@ -490,28 +483,28 @@ public class JDBCUserDatabase extends AbstractUserDatabase {
             m_renameProfile = "UPDATE " + userTable + " SET " + m_loginName + "=?," + m_modified + "=? WHERE " + m_loginName
                               + "=?";
             m_renameRoles = "UPDATE " + roleTable + " SET " + m_loginName + "=? WHERE " + m_loginName + "=?";
-        } catch( NamingException e ) {
+        } catch( final NamingException e ) {
             log.error( "JDBCUserDatabase initialization error: " + e.getMessage() );
             throw new NoRequiredPropertyException( PROP_DB_DATASOURCE, "JDBCUserDatabase initialization error: " + e.getMessage() );
         }
 
         // Test connection by doing a quickie select
-        try( Connection conn = m_ds.getConnection(); PreparedStatement ps = conn.prepareStatement( m_findAll ) ) {
-        } catch( SQLException e ) {
+        try( final Connection conn = m_ds.getConnection(); final PreparedStatement ps = conn.prepareStatement( m_findAll ) ) {
+        } catch( final SQLException e ) {
             log.error( "DB connectivity error: " + e.getMessage() );
             throw new WikiSecurityException("DB connectivity error: " + e.getMessage(), e );
         }
         log.info( "JDBCUserDatabase initialized from JNDI DataSource: " + jndiName );
 
         // Determine if the datasource supports commits
-        try( Connection conn = m_ds.getConnection() ) {
-            DatabaseMetaData dmd = conn.getMetaData();
+        try( final Connection conn = m_ds.getConnection() ) {
+            final DatabaseMetaData dmd = conn.getMetaData();
             if( dmd.supportsTransactions() ) {
                 m_supportsCommits = true;
                 conn.setAutoCommit( false );
                 log.info( "JDBCUserDatabase supports transactions. Good; we will use them." );
             }
-        } catch( SQLException e ) {
+        } catch( final SQLException e ) {
             log.warn( "JDBCUserDatabase warning: user database doesn't seem to support transactions. Reason: " + e.getMessage() );
         }
     }
@@ -520,30 +513,30 @@ public class JDBCUserDatabase extends AbstractUserDatabase {
      * @see org.apache.wiki.auth.user.UserDatabase#rename(String, String)
      */
     @Override
-    public void rename( String loginName, String newName ) throws NoSuchPrincipalException, DuplicateUserException, WikiSecurityException {
+    public void rename( final String loginName, final String newName ) throws NoSuchPrincipalException, DuplicateUserException, WikiSecurityException {
         // Get the existing user; if not found, throws NoSuchPrincipalException
-        UserProfile profile = findByLoginName( loginName );
+        final UserProfile profile = findByLoginName( loginName );
 
         // Get user with the proposed name; if found, it's a collision
         try {
-            UserProfile otherProfile = findByLoginName( newName );
+            final UserProfile otherProfile = findByLoginName( newName );
             if( otherProfile != null ) {
                 throw new DuplicateUserException( "security.error.cannot.rename", newName );
             }
-        } catch( NoSuchPrincipalException e ) {
+        } catch( final NoSuchPrincipalException e ) {
             // Good! That means it's safe to save using the new name
         }
 
-        try( Connection conn = m_ds.getConnection(); 
-             PreparedStatement ps1 = conn.prepareStatement( m_renameProfile );
-             PreparedStatement ps2 = conn.prepareStatement( m_renameRoles ) )
+        try( final Connection conn = m_ds.getConnection();
+             final PreparedStatement ps1 = conn.prepareStatement( m_renameProfile );
+             final PreparedStatement ps2 = conn.prepareStatement( m_renameRoles ) )
         {
             if( m_supportsCommits ) {
                 conn.setAutoCommit( false );
             }
 
-            Timestamp ts = new Timestamp( System.currentTimeMillis() );
-            Date modDate = new Date( ts.getTime() );
+            final Timestamp ts = new Timestamp( System.currentTimeMillis() );
+            final Date modDate = new Date( ts.getTime() );
 
             // Change the login ID for the user record
             ps1.setString( 1, newName );
@@ -564,7 +557,7 @@ public class JDBCUserDatabase extends AbstractUserDatabase {
             if( m_supportsCommits ) {
                 conn.commit();
             }
-        } catch( SQLException e ) {
+        } catch( final SQLException e ) {
             throw new WikiSecurityException( e.getMessage(), e );
         }
     }
@@ -573,23 +566,23 @@ public class JDBCUserDatabase extends AbstractUserDatabase {
      * @see org.apache.wiki.auth.user.UserDatabase#save(org.apache.wiki.auth.user.UserProfile)
      */
     @Override
-    public void save( UserProfile profile ) throws WikiSecurityException {
-        String initialRole = "Authenticated";
+    public void save( final UserProfile profile ) throws WikiSecurityException {
+        final String initialRole = "Authenticated";
 
         // Figure out which prepared statement to use & execute it
-        String loginName = profile.getLoginName();
+        final String loginName = profile.getLoginName();
         UserProfile existingProfile = null;
 
         try {
             existingProfile = findByLoginName( loginName );
-        } catch( NoSuchPrincipalException e ) {
+        } catch( final NoSuchPrincipalException e ) {
             // Existing profile will be null
         }
 
         // Get a clean password from the passed profile.
         // Blank password is the same as null, which means we re-use the existing one.
         String password = profile.getPassword();
-        String existingPassword = (existingProfile == null) ? null : existingProfile.getPassword();
+        final String existingPassword = (existingProfile == null) ? null : existingProfile.getPassword();
         if( NOTHING.equals( password ) ) {
             password = null;
         }
@@ -602,19 +595,19 @@ public class JDBCUserDatabase extends AbstractUserDatabase {
             password = getHash( password );
         }
 
-        try( Connection conn = m_ds.getConnection();
-             PreparedStatement ps1 = conn.prepareStatement( m_insertProfile );
-             PreparedStatement ps2 = conn.prepareStatement( m_findRoles );
-             PreparedStatement ps3 = conn.prepareStatement( m_insertRole );
-             PreparedStatement ps4 = conn.prepareStatement( m_updateProfile ) )
+        try( final Connection conn = m_ds.getConnection();
+             final PreparedStatement ps1 = conn.prepareStatement( m_insertProfile );
+             final PreparedStatement ps2 = conn.prepareStatement( m_findRoles );
+             final PreparedStatement ps3 = conn.prepareStatement( m_insertRole );
+             final PreparedStatement ps4 = conn.prepareStatement( m_updateProfile ) )
         {
             if( m_supportsCommits ) {
                 conn.setAutoCommit( false );
             }
 
-            Timestamp ts = new Timestamp( System.currentTimeMillis() );
-            Date modDate = new Date( ts.getTime() );
-            java.sql.Date lockExpiry = profile.getLockExpiry() == null ? null : new java.sql.Date( profile.getLockExpiry().getTime() );
+            final Timestamp ts = new Timestamp( System.currentTimeMillis() );
+            final Date modDate = new Date( ts.getTime() );
+            final java.sql.Date lockExpiry = profile.getLockExpiry() == null ? null : new java.sql.Date( profile.getLockExpiry().getTime() );
             if( existingProfile == null )
             {
                 // User is new: insert new user record
@@ -627,7 +620,7 @@ public class JDBCUserDatabase extends AbstractUserDatabase {
                 ps1.setString( 7, profile.getLoginName() );
                 try {
                     ps1.setString( 8, Serializer.serializeToBase64( profile.getAttributes() ) );
-                } catch ( IOException e ) {
+                } catch ( final IOException e ) {
                     throw new WikiSecurityException( "Could not save user profile attribute. Reason: " + e.getMessage(), e );
                 }
                 ps1.setTimestamp( 9, ts );
@@ -636,7 +629,7 @@ public class JDBCUserDatabase extends AbstractUserDatabase {
                 // Insert new role record
                 ps2.setString( 1, profile.getLoginName() );
                 int roles = 0;
-                try ( ResultSet rs = ps2.executeQuery() ) {
+                try ( final ResultSet rs = ps2.executeQuery() ) {
                     while ( rs.next() ) {
                         roles++;
                     }
@@ -663,7 +656,7 @@ public class JDBCUserDatabase extends AbstractUserDatabase {
                 {
                     ps4.setString( 8, Serializer.serializeToBase64( profile.getAttributes() ) );
                 }
-                catch ( IOException e )
+                catch ( final IOException e )
                 {
                     throw new WikiSecurityException( "Could not save user profile attribute. Reason: " + e.getMessage(), e );
                 }
@@ -679,7 +672,7 @@ public class JDBCUserDatabase extends AbstractUserDatabase {
                 conn.commit();
             }
         }
-        catch( SQLException e )
+        catch( final SQLException e )
         {
             throw new WikiSecurityException( e.getMessage(), e );
         }
@@ -694,12 +687,12 @@ public class JDBCUserDatabase extends AbstractUserDatabase {
      * @return the resolved UserProfile
      * @throws SQLException
      */
-    private UserProfile findByPreparedStatement( String sql, Object index ) throws NoSuchPrincipalException
+    private UserProfile findByPreparedStatement( final String sql, final Object index ) throws NoSuchPrincipalException
     {
         UserProfile profile = null;
         boolean found = false;
         boolean unique = true;
-        try( Connection conn = m_ds.getConnection(); PreparedStatement ps = conn.prepareStatement( sql ) ) {
+        try( final Connection conn = m_ds.getConnection(); final PreparedStatement ps = conn.prepareStatement( sql ) ) {
             if( m_supportsCommits ) {
                 conn.setAutoCommit( false );
             }
@@ -714,7 +707,7 @@ public class JDBCUserDatabase extends AbstractUserDatabase {
             }
             
             // Go and get the record!
-            try ( ResultSet rs = ps.executeQuery() ) {
+            try ( final ResultSet rs = ps.executeQuery() ) {
                 while ( rs.next() ) {
                     if( profile != null ) {
                         unique = false;
@@ -731,25 +724,25 @@ public class JDBCUserDatabase extends AbstractUserDatabase {
                     profile.setEmail( rs.getString( m_email ) );
                     profile.setFullname( rs.getString( m_fullName ) );
                     profile.setLastModified( rs.getTimestamp( m_modified ) );
-                    Date lockExpiry = rs.getDate( m_lockExpiry );
+                    final Date lockExpiry = rs.getDate( m_lockExpiry );
                     profile.setLockExpiry( rs.wasNull() ? null : lockExpiry );
                     profile.setLoginName( rs.getString( m_loginName ) );
                     profile.setPassword( rs.getString( m_password ) );
                     
                     // Fetch the user attributes
-                    String rawAttributes = rs.getString( m_attributes );
+                    final String rawAttributes = rs.getString( m_attributes );
                     if ( rawAttributes != null ) {
                         try {
-                            Map<String,? extends Serializable> attributes = Serializer.deserializeFromBase64( rawAttributes );
+                            final Map<String,? extends Serializable> attributes = Serializer.deserializeFromBase64( rawAttributes );
                             profile.getAttributes().putAll( attributes );
-                        } catch ( IOException e ) {
+                        } catch ( final IOException e ) {
                             log.error( "Could not parse user profile attributes!", e );
                         }
                     }
                     found = true;
                 }
             }
-        } catch( SQLException e ) {
+        } catch( final SQLException e ) {
             throw new NoSuchPrincipalException( e.getMessage() );
         }
 
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/user/UserDatabase.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/user/UserDatabase.java
index 1312ecc..c8928cc 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/auth/user/UserDatabase.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/user/UserDatabase.java
@@ -18,103 +18,89 @@
  */
 package org.apache.wiki.auth.user;
 
-import java.security.Principal;
-import java.util.Properties;
-
-import org.apache.wiki.WikiEngine;
+import org.apache.wiki.api.core.Engine;
 import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
 import org.apache.wiki.auth.NoSuchPrincipalException;
 import org.apache.wiki.auth.WikiSecurityException;
 
+import java.security.Principal;
+import java.util.Properties;
+
 /**
  * Defines an interface for loading, persisting and storing users.
+ *
  * @since 2.3
  */
-public interface UserDatabase
-{
+public interface UserDatabase {
 
     /**
-     * Looks up and deletes the first {@link UserProfile} in the user database
-     * that matches a profile having a given login name. If the user database
-     * does not contain a user with a matching attribute, throws a
-     * {@link NoSuchPrincipalException}. This method is intended to be atomic;
-     * results cannot be partially committed. If the commit fails, it should
-     * roll back its state appropriately. Implementing classes that persist
-     * to the file system may wish to make this method <code>synchronized</code>.
+     * Looks up and deletes the first {@link UserProfile} in the user database that matches a profile having a given login name. If the
+     * user database does not contain a user with a matching attribute, throws a {@link NoSuchPrincipalException}. This method is intended
+     * to be atomic; results cannot be partially committed. If the commit fails, it should roll back its state appropriately. Implementing
+     * classes that persist to the file system may wish to make this method <code>synchronized</code>.
+     *
      * @param loginName the login name of the user profile that shall be deleted
      */
     void deleteByLoginName( String loginName ) throws NoSuchPrincipalException, WikiSecurityException;
 
     /**
      * <p>
-     * Looks up the Principals representing a user from the user database. These
-     * are defined as a set of Principals manufactured from the login name, full
-     * name, and wiki name. The order of the Principals returned is not
-     * significant. If the user database does not contain a user with the
-     * supplied identifier, throws a {@link NoSuchPrincipalException}.
+     * Looks up the Principals representing a user from the user database. These are defined as a set of Principals manufactured from the
+     * login name, full name, and wiki name. The order of the Principals returned is not significant. If the user database does not contain
+     * a user with the supplied identifier, throws a {@link NoSuchPrincipalException}.
      * </p>
      * <p>
-     * Note that if an implememtation wishes to mark one of the returned
-     * Principals as representing the user's common name, it should instantiate
-     * this Principal using
-     * {@link org.apache.wiki.auth.WikiPrincipal#WikiPrincipal(String, String)}
-     * with the <code>type</code> parameter set to
-     * {@link org.apache.wiki.auth.WikiPrincipal#WIKI_NAME}. The method
-     * {@link org.apache.wiki.WikiSession#getUserPrincipal()} will return this
-     * principal as the "primary" principal. Note that this method can also be
-     * used to mark a WikiPrincipal as a login name or a wiki name.
+     * Note that if an implememtation wishes to mark one of the returned Principals as representing the user's common name, it should
+     * instantiate this Principal using {@link org.apache.wiki.auth.WikiPrincipal#WikiPrincipal(String, String)} with the <code>type</code>
+     * parameter set to {@link org.apache.wiki.auth.WikiPrincipal#WIKI_NAME}. The method
+     * {@link org.apache.wiki.WikiSession#getUserPrincipal()} will return this principal as the "primary" principal. Note that this method
+     * can also be used to mark a WikiPrincipal as a login name or a wiki name.
      * </p>
-     * @param identifier the name of the user to retrieve; this corresponds to
-     *            value returned by the user profile's
-     *            {@link UserProfile#getLoginName()} method.
+     *
+     * @param identifier the name of the user to retrieve; this corresponds to value returned by the user profile's {@link UserProfile#getLoginName()} method.
      * @return the array of Principals representing the user's identities
      */
     Principal[] getPrincipals( String identifier ) throws NoSuchPrincipalException;
 
     /**
-     * Returns all WikiNames that are stored in the UserDatabase
-     * as an array of Principal objects. If the database does not
-     * contain any profiles, this method will return a zero-length
-     * array.
+     * Returns all WikiNames that are stored in the UserDatabase as an array of Principal objects. If the database does not
+     * contain any profiles, this method will return a zero-length array.
+     *
      * @return the WikiNames
      */
     Principal[] getWikiNames() throws WikiSecurityException;
 
     /**
-     * Looks up and returns the first {@link UserProfile} in the user database
-     * that whose login name, full name, or wiki name matches the supplied
-     * string. This method provides a "forgiving" search algorithm for resolving
-     * Principal names when the exact profile attribute that supplied the name
-     * is unknown.
+     * Looks up and returns the first {@link UserProfile} in the user database that whose login name, full name, or wiki name matches the
+     * supplied string. This method provides a "forgiving" search algorithm for resolving Principal names when the exact profile attribute
+     * that supplied the name is unknown.
+     *
      * @param index the login name, full name, or wiki name
      */
     UserProfile find( String index ) throws NoSuchPrincipalException;
 
     /**
-     * Looks up and returns the first {@link UserProfile} in the user database
-     * that matches a profile having a given e-mail address. If the user
-     * database does not contain a user with a matching attribute, throws a
-     * {@link NoSuchPrincipalException}.
+     * Looks up and returns the first {@link UserProfile} in the user database that matches a profile having a given e-mail address. If
+     * the user database does not contain a user with a matching attribute, throws a {@link NoSuchPrincipalException}.
+     *
      * @param index the e-mail address of the desired user profile
      * @return the user profile
      */
     UserProfile findByEmail( String index ) throws NoSuchPrincipalException;
 
     /**
-     * Looks up and returns the first {@link UserProfile} in the user database
-     * that matches a profile having a given login name. If the user database
-     * does not contain a user with a matching attribute, throws a
-     * {@link NoSuchPrincipalException}.
+     * Looks up and returns the first {@link UserProfile} in the user database that matches a profile having a given login name. If the
+     * user database does not contain a user with a matching attribute, throws a {@link NoSuchPrincipalException}.
+     *
      * @param index the login name of the desired user profile
      * @return the user profile
      */
     UserProfile findByLoginName( String index ) throws NoSuchPrincipalException;
 
     /**
-     * Looks up and returns the first {@link UserProfile} in the user database
-     * that matches a profile having a given unique ID (uid). If the user database
-     * does not contain a user with a unique ID, it throws a
-     * {@link NoSuchPrincipalException}.
+     * Looks up and returns the first {@link UserProfile} in the user database that matches a profile having a given unique ID (uid). If
+     * the user database does not contain a user with a unique ID, it throws a {@link NoSuchPrincipalException}.
+     *
      * @param uid the unique identifier of the desired user profile
      * @return the user profile
      * @since 2.8
@@ -122,95 +108,75 @@ public interface UserDatabase
     UserProfile findByUid( String uid ) throws NoSuchPrincipalException;
     
     /**
-     * Looks up and returns the first {@link UserProfile} in the user database
-     * that matches a profile having a given wiki name. If the user database
-     * does not contain a user with a matching attribute, throws a
-     * {@link NoSuchPrincipalException}.
+     * Looks up and returns the first {@link UserProfile} in the user database that matches a profile having a given wiki name. If the user
+     * database does not contain a user with a matching attribute, throws a {@link NoSuchPrincipalException}.
+     *
      * @param index the wiki name of the desired user profile
      * @return the user profile
      */
     UserProfile findByWikiName( String index ) throws NoSuchPrincipalException;
 
     /**
-     * Looks up and returns the first {@link UserProfile} in the user database
-     * that matches a profile having a given full name. If the user database
-     * does not contain a user with a matching attribute, throws a
-     * {@link NoSuchPrincipalException}.
+     * Looks up and returns the first {@link UserProfile} in the user database that matches a profile having a given full name. If the user
+     * database does not contain a user with a matching attribute, throws a {@link NoSuchPrincipalException}.
+     *
      * @param index the fill name of the desired user profile
      * @return the user profile
      */
     UserProfile findByFullName( String index ) throws NoSuchPrincipalException;
 
-    /**
-     * Initializes the user database based on values from a Properties object.
-     */
-    void initialize( WikiEngine engine, Properties props ) throws NoRequiredPropertyException, WikiSecurityException;
+    /** Initializes the user database based on values from a Properties object. */
+    void initialize( Engine engine, Properties props ) throws NoRequiredPropertyException, WikiSecurityException;
 
     /**
-     * Factory method that instantiates a new user profile.
-     * The {@link UserProfile#isNew()} method of profiles created using
+     * Factory method that instantiates a new user profile. The {@link UserProfile#isNew()} method of profiles created using
      * this method should return <code>true</code>.
      */
     UserProfile newProfile();
 
     /**
-     * <p>Renames a {@link UserProfile} in the user database by changing
-     * the profile's login name. Because the login name is the profile's unique
-     * identifier, implementations should verify that the identifier is
-     * "safe" to change before actually changing it. Specifically: the profile
-     * with the supplied login name must already exist, and the proposed new
-     * name must not be in use by another profile.</p>
-     * <p>This method is intended to be atomic; results cannot be partially committed.
-     * If the commit fails, it should roll back its state appropriately.
-     * Implementing classes that persist to the file system may wish to make
-     * this method <code>synchronized</code>.</p>
+     * <p>Renames a {@link UserProfile} in the user database by changing the profile's login name. Because the login name is the profile's
+     * unique identifier, implementations should verify that the identifier is "safe" to change before actually changing it. Specifically:
+     * the profile with the supplied login name must already exist, and the proposed new name must not be in use by another profile.</p>
+     * <p>This method is intended to be atomic; results cannot be partially committed. If the commit fails, it should roll back its state
+     * appropriately. Implementing classes that persist to the file system may wish to make this method <code>synchronized</code>.</p>
+     *
      * @param loginName the existing login name for the profile
      * @param newName the proposed new login name
-     * @throws NoSuchPrincipalException if the user profile identified by
-     * <code>loginName</code> does not exist
-     * @throws DuplicateUserException if another user profile with the
-     * proposed new login name already exists
-     * @throws WikiSecurityException if the profile cannot be renamed for
-     * any reason, such as an I/O error, database connection failure
+     * @throws NoSuchPrincipalException if the user profile identified by <code>loginName</code> does not exist
+     * @throws DuplicateUserException if another user profile with the proposed new login name already exists
+     * @throws WikiSecurityException if the profile cannot be renamed for any reason, such as an I/O error, database connection failure
      * or lack of support for renames.
      */
     void rename( String loginName, String newName ) throws NoSuchPrincipalException, DuplicateUserException, WikiSecurityException;
 
     /**
      * <p>
-     * Saves a {@link UserProfile}to the user database, overwriting the
-     * existing profile if it exists. The user name under which the profile
-     * should be saved is returned by the supplied profile's
-     * {@link UserProfile#getLoginName()} method.
+     * Saves a {@link UserProfile}to the user database, overwriting the existing profile if it exists. The user name under which the profile
+     * should be saved is returned by the supplied profile's {@link UserProfile#getLoginName()} method.
      * </p>
      * <p>
-     * The database implementation is responsible for detecting potential
-     * duplicate user profiles; specifically, the login name, wiki name, and
-     * full name must be unique. The implementation is not required to check for
-     * validity of passwords or e-mail addresses. Special case: if the profile
-     * already exists and the password is null, it should retain its previous
-     * value, rather than being set to null.
+     * The database implementation is responsible for detecting potential duplicate user profiles; specifically, the login name, wiki name,
+     * and full name must be unique. The implementation is not required to check for validity of passwords or e-mail addresses. Special
+     * case: if the profile already exists and the password is null, it should retain its previous value, rather than being set to null.
      * </p>
-     * <p>Implementations are <em>required</em> to time-stamp the creation
-     * or modification fields of the UserProfile./p>
-     * <p>This method is intended to be atomic; results cannot be partially committed.
-     * If the commit fails, it should roll back its state appropriately.
-     * Implementing classes that persist to the file system may wish to make
-     * this method <code>synchronized</code>.</p>
+     * <p>Implementations are <em>required</em> to time-stamp the creation or modification fields of the UserProfile./p>
+     * <p>This method is intended to be atomic; results cannot be partially committed. If the commit fails, it should roll back its state
+     * appropriately. Implementing classes that persist to the file system may wish to make this method <code>synchronized</code>.</p>
+     *
      * @param profile the user profile to save
      * @throws WikiSecurityException if the profile cannot be saved
      */
     void save( UserProfile profile ) throws WikiSecurityException;
 
     /**
-     * Determines whether a supplied user password is valid, given a login name
-     * and password. It is up to the implementing class to determine how the
-     * comparison should be made. For example, the password might be hashed
-     * before comparing it to the value persisted in the back-end data store.
+     * Determines whether a supplied user password is valid, given a login name and password. It is up to the implementing class to
+     * determine how the comparison should be made. For example, the password might be hashed before comparing it to the value persisted
+     * in the back-end data store.
+     *
      * @param loginName the login name
      * @param password the password
-     * @return <code>true</code> if the password is valid, <code>false</code>
-     *         otherwise
+     * @return <code>true</code> if the password is valid, <code>false</code> otherwise
      */
     boolean validatePassword( String loginName, String password );
 
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/auth/user/XMLUserDatabase.java b/jspwiki-main/src/main/java/org/apache/wiki/auth/user/XMLUserDatabase.java
index 466587c..3357407 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/auth/user/XMLUserDatabase.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/auth/user/XMLUserDatabase.java
@@ -19,7 +19,7 @@
 package org.apache.wiki.auth.user;
 
 import org.apache.commons.lang3.StringUtils;
-import org.apache.wiki.WikiEngine;
+import org.apache.wiki.api.core.Engine;
 import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
 import org.apache.wiki.auth.NoSuchPrincipalException;
 import org.apache.wiki.auth.WikiPrincipal;
@@ -115,17 +115,17 @@ public class XMLUserDatabase extends AbstractUserDatabase {
      * {@link NoSuchPrincipalException}.
      * @param loginName the login name of the user profile that shall be deleted
      */
-    public synchronized void deleteByLoginName( String loginName ) throws NoSuchPrincipalException, WikiSecurityException
+    @Override public synchronized void deleteByLoginName( final String loginName ) throws NoSuchPrincipalException, WikiSecurityException
     {
         if ( c_dom == null )
         {
             throw new WikiSecurityException( "FATAL: database does not exist" );
         }
 
-        NodeList users = c_dom.getDocumentElement().getElementsByTagName( USER_TAG );
+        final NodeList users = c_dom.getDocumentElement().getElementsByTagName( USER_TAG );
         for( int i = 0; i < users.getLength(); i++ )
         {
-            Element user = (Element) users.item( i );
+            final Element user = (Element) users.item( i );
             if ( user.getAttribute( LOGIN_NAME ).equals( loginName ) )
             {
                 c_dom.getDocumentElement().removeChild(user);
@@ -147,9 +147,9 @@ public class XMLUserDatabase extends AbstractUserDatabase {
      * @return the user profile
      * @see org.apache.wiki.auth.user.UserDatabase#findByEmail(String)
      */
-    public UserProfile findByEmail( String index ) throws NoSuchPrincipalException
+    @Override public UserProfile findByEmail( final String index ) throws NoSuchPrincipalException
     {
-        UserProfile profile = findByAttribute( EMAIL, index );
+        final UserProfile profile = findByAttribute( EMAIL, index );
         if ( profile != null )
         {
             return profile;
@@ -166,9 +166,9 @@ public class XMLUserDatabase extends AbstractUserDatabase {
      * @return the user profile
      * @see org.apache.wiki.auth.user.UserDatabase#findByFullName(java.lang.String)
      */
-    public UserProfile findByFullName( String index ) throws NoSuchPrincipalException
+    @Override public UserProfile findByFullName( final String index ) throws NoSuchPrincipalException
     {
-        UserProfile profile = findByAttribute( FULL_NAME, index );
+        final UserProfile profile = findByAttribute( FULL_NAME, index );
         if ( profile != null )
         {
             return profile;
@@ -185,9 +185,9 @@ public class XMLUserDatabase extends AbstractUserDatabase {
      * @return the user profile
      * @see org.apache.wiki.auth.user.UserDatabase#findByLoginName(java.lang.String)
      */
-    public UserProfile findByLoginName( String index ) throws NoSuchPrincipalException
+    @Override public UserProfile findByLoginName( final String index ) throws NoSuchPrincipalException
     {
-        UserProfile profile = findByAttribute( LOGIN_NAME, index );
+        final UserProfile profile = findByAttribute( LOGIN_NAME, index );
         if ( profile != null )
         {
             return profile;
@@ -198,9 +198,9 @@ public class XMLUserDatabase extends AbstractUserDatabase {
     /**
      * {@inheritDoc}
      */
-    public UserProfile findByUid( String uid ) throws NoSuchPrincipalException
+    @Override public UserProfile findByUid( final String uid ) throws NoSuchPrincipalException
     {
-        UserProfile profile = findByAttribute( UID, uid );
+        final UserProfile profile = findByAttribute( UID, uid );
         if ( profile != null )
         {
             return profile;
@@ -217,9 +217,9 @@ public class XMLUserDatabase extends AbstractUserDatabase {
      * @return the user profile
      * @see org.apache.wiki.auth.user.UserDatabase#findByWikiName(java.lang.String)
      */
-    public UserProfile findByWikiName( String index ) throws NoSuchPrincipalException
+    @Override public UserProfile findByWikiName( final String index ) throws NoSuchPrincipalException
     {
-        UserProfile profile = findByAttribute( WIKI_NAME, index );
+        final UserProfile profile = findByAttribute( WIKI_NAME, index );
         if ( profile != null )
         {
             return profile;
@@ -235,25 +235,25 @@ public class XMLUserDatabase extends AbstractUserDatabase {
      * @return the WikiNames
      * @throws WikiSecurityException In case things fail.
      */
-    public Principal[] getWikiNames() throws WikiSecurityException
+    @Override public Principal[] getWikiNames() throws WikiSecurityException
     {
         if ( c_dom == null )
         {
             throw new IllegalStateException( "FATAL: database does not exist" );
         }
-        SortedSet<Principal> principals = new TreeSet<Principal>();
-        NodeList users = c_dom.getElementsByTagName( USER_TAG );
+        final SortedSet<Principal> principals = new TreeSet<>();
+        final NodeList users = c_dom.getElementsByTagName( USER_TAG );
         for( int i = 0; i < users.getLength(); i++ )
         {
-            Element user = (Element) users.item( i );
-            String wikiName = user.getAttribute( WIKI_NAME );
+            final Element user = (Element) users.item( i );
+            final String wikiName = user.getAttribute( WIKI_NAME );
             if ( wikiName == null )
             {
                 log.warn( "Detected null wiki name in XMLUserDataBase. Check your user database." );
             }
             else
             {
-                Principal principal = new WikiPrincipal( wikiName, WikiPrincipal.WIKI_NAME );
+                final Principal principal = new WikiPrincipal( wikiName, WikiPrincipal.WIKI_NAME );
                 principals.add( principal );
             }
         }
@@ -264,115 +264,83 @@ public class XMLUserDatabase extends AbstractUserDatabase {
      * Initializes the user database based on values from a Properties object.
      * The properties object must contain a file path to the XML database file
      * whose key is {@link #PROP_USERDATABASE}.
-     * @see org.apache.wiki.auth.user.UserDatabase#initialize(org.apache.wiki.WikiEngine,
-     *      java.util.Properties)
+     * @see org.apache.wiki.auth.user.UserDatabase#initialize(org.apache.wiki.api.core.Engine, java.util.Properties)
      * @throws NoRequiredPropertyException if the user database cannot be located, parsed, or opened
      */
-    public void initialize( WikiEngine engine, Properties props ) throws NoRequiredPropertyException
-    {
-        File defaultFile = null;
-        if( engine.getRootPath() == null )
-        {
-            log.warn( "Cannot identify JSPWiki root path"  );
+    @Override public void initialize( final Engine engine, final Properties props ) throws NoRequiredPropertyException {
+        final File defaultFile;
+        if( engine.getRootPath() == null ) {
+            log.warn( "Cannot identify JSPWiki root path" );
             defaultFile = new File( "WEB-INF/" + DEFAULT_USERDATABASE ).getAbsoluteFile();
-        }
-        else
-        {
+        } else {
             defaultFile = new File( engine.getRootPath() + "/WEB-INF/" + DEFAULT_USERDATABASE );
         }
 
         // Get database file location
-        String file = TextUtil.getStringProperty(props, PROP_USERDATABASE, defaultFile.getAbsolutePath());
-        if( file == null )
-        {
-            log.warn( "XML user database property " + PROP_USERDATABASE + " not found; trying " + defaultFile  );
+        final String file = TextUtil.getStringProperty( props, PROP_USERDATABASE, defaultFile.getAbsolutePath() );
+        if( file == null ) {
+            log.warn( "XML user database property " + PROP_USERDATABASE + " not found; trying " + defaultFile );
             c_file = defaultFile;
-        }
-        else
-        {
+        } else {
             c_file = new File( file );
         }
 
-        log.info("XML user database at "+c_file.getAbsolutePath());
+        log.info( "XML user database at " + c_file.getAbsolutePath() );
 
         buildDOM();
         sanitizeDOM();
     }
 
-    private void buildDOM()
-    {
+    private void buildDOM() {
         // Read DOM
-        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
         factory.setValidating( false );
         factory.setExpandEntityReferences( false );
         factory.setIgnoringComments( true );
         factory.setNamespaceAware( false );
-        try
-        {
+        try {
             c_dom = factory.newDocumentBuilder().parse( c_file );
             log.debug( "Database successfully initialized" );
             c_lastModified = c_file.lastModified();
-            c_lastCheck    = System.currentTimeMillis();
-        }
-        catch( ParserConfigurationException e )
-        {
+            c_lastCheck = System.currentTimeMillis();
+        } catch( final ParserConfigurationException e ) {
             log.error( "Configuration error: " + e.getMessage() );
-        }
-        catch( SAXException e )
-        {
+        } catch( final SAXException e ) {
             log.error( "SAX error: " + e.getMessage() );
-        }
-        catch( FileNotFoundException e )
-        {
-            log.info("User database not found; creating from scratch...");
-        }
-        catch( IOException e )
-        {
+        } catch( final FileNotFoundException e ) {
+            log.info( "User database not found; creating from scratch..." );
+        } catch( final IOException e ) {
             log.error( "IO error: " + e.getMessage() );
         }
-        if ( c_dom == null )
-        {
-            try
-            {
-                //
+        if( c_dom == null ) {
+            try {
                 //  Create the DOM from scratch
-                //
                 c_dom = factory.newDocumentBuilder().newDocument();
-                c_dom.appendChild( c_dom.createElement( "users") );
-            }
-            catch( ParserConfigurationException e )
-            {
+                c_dom.appendChild( c_dom.createElement( "users" ) );
+            } catch( final ParserConfigurationException e ) {
                 log.fatal( "Could not create in-memory DOM" );
             }
         }
     }
 
-    private void saveDOM() throws WikiSecurityException
-    {
-        if ( c_dom == null )
-        {
+    private void saveDOM() throws WikiSecurityException {
+        if( c_dom == null ) {
             log.fatal( "User database doesn't exist in memory." );
         }
 
-        File newFile = new File( c_file.getAbsolutePath() + ".new" );
-        try
-        (
-            BufferedWriter io = new BufferedWriter( new OutputStreamWriter (
-                    new FileOutputStream( newFile ), "UTF-8" ) );
-        )
-        {
+        final File newFile = new File( c_file.getAbsolutePath() + ".new" );
+        try( final BufferedWriter io = new BufferedWriter( new OutputStreamWriter( new FileOutputStream( newFile ), "UTF-8" ) ) ) {
 
             // Write the file header and document root
-            io.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
-            io.write("<users>\n");
+            io.write( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" );
+            io.write( "<users>\n" );
 
             // Write each profile as a <user> node
-            Element root = c_dom.getDocumentElement();
-            NodeList nodes = root.getElementsByTagName( USER_TAG );
-            for( int i = 0; i < nodes.getLength(); i++ )
-            {
-                Element user = (Element)nodes.item( i );
-                io.write( "    <" + USER_TAG + " ");
+            final Element root = c_dom.getDocumentElement();
+            final NodeList nodes = root.getElementsByTagName( USER_TAG );
+            for( int i = 0; i < nodes.getLength(); i++ ) {
+                final Element user = ( Element )nodes.item( i );
+                io.write( "    <" + USER_TAG + " " );
                 io.write( UID );
                 io.write( "=\"" + user.getAttribute( UID ) + "\" " );
                 io.write( LOGIN_NAME );
@@ -392,43 +360,34 @@ public class XMLUserDatabase extends AbstractUserDatabase {
                 io.write( LOCK_EXPIRY );
                 io.write( "=\"" + user.getAttribute( LOCK_EXPIRY ) + "\" " );
                 io.write( ">" );
-                NodeList attributes = user.getElementsByTagName( ATTRIBUTES_TAG );
-                for ( int j = 0; j < attributes.getLength(); j++ )
-                {
-                    Element attribute = (Element)attributes.item( j );
-                    String value = extractText( attribute );
+                final NodeList attributes = user.getElementsByTagName( ATTRIBUTES_TAG );
+                for( int j = 0; j < attributes.getLength(); j++ ) {
+                    final Element attribute = ( Element )attributes.item( j );
+                    final String value = extractText( attribute );
                     io.write( "\n        <" + ATTRIBUTES_TAG + ">" );
                     io.write( value );
                     io.write( "</" + ATTRIBUTES_TAG + ">" );
                 }
-                io.write("\n    </" +USER_TAG + ">\n");
+                io.write( "\n    </" + USER_TAG + ">\n" );
             }
-            io.write("</users>");
-            io.close();
-        }
-        catch ( IOException e )
-        {
+            io.write( "</users>" );
+        } catch( final IOException e ) {
             throw new WikiSecurityException( e.getLocalizedMessage(), e );
         }
 
         // Copy new file over old version
-        File backup = new File( c_file.getAbsolutePath() + ".old" );
-        if ( backup.exists() )
-        {
-            if ( !backup.delete() )
-            {
+        final File backup = new File( c_file.getAbsolutePath() + ".old" );
+        if( backup.exists() ) {
+            if( !backup.delete() ) {
                 log.error( "Could not delete old user database backup: " + backup );
             }
         }
-        if ( !c_file.renameTo( backup ) )
-        {
+        if( !c_file.renameTo( backup ) ) {
             log.error( "Could not create user database backup: " + backup );
         }
-        if ( !newFile.renameTo( c_file ) )
-        {
+        if( !newFile.renameTo( c_file ) ) {
             log.error( "Could not save database: " + backup + " restoring backup." );
-            if ( !backup.renameTo( c_file ) )
-            {
+            if( !backup.renameTo( c_file ) ) {
                 log.error( "Restore failed. Check the file permissions." );
             }
             log.error( "Could not save database: " + c_file + ". Check the file permissions" );
@@ -438,16 +397,12 @@ public class XMLUserDatabase extends AbstractUserDatabase {
     private long c_lastCheck    = 0;
     private long c_lastModified = 0;
 
-    private void checkForRefresh()
-    {
-        long time = System.currentTimeMillis();
+    private void checkForRefresh() {
+        final long time = System.currentTimeMillis();
+        if( time - c_lastCheck > 60 * 1000L ) {
+            final long lastModified = c_file.lastModified();
 
-        if( time - c_lastCheck > 60*1000L )
-        {
-            long lastModified = c_file.lastModified();
-
-            if( lastModified > c_lastModified )
-            {
+            if( lastModified > c_lastModified ) {
                 buildDOM();
             }
         }
@@ -456,7 +411,7 @@ public class XMLUserDatabase extends AbstractUserDatabase {
     /**
      * @see org.apache.wiki.auth.user.UserDatabase#rename(String, String)
      */
-    public synchronized void rename(String loginName, String newName) throws NoSuchPrincipalException, DuplicateUserException, WikiSecurityException
+    @Override public synchronized void rename( final String loginName, final String newName) throws NoSuchPrincipalException, DuplicateUserException, WikiSecurityException
     {
         if ( c_dom == null )
         {
@@ -466,31 +421,31 @@ public class XMLUserDatabase extends AbstractUserDatabase {
         checkForRefresh();
 
         // Get the existing user; if not found, throws NoSuchPrincipalException
-        UserProfile profile = findByLoginName( loginName );
+        final UserProfile profile = findByLoginName( loginName );
 
         // Get user with the proposed name; if found, it's a collision
         try
         {
-            UserProfile otherProfile = findByLoginName( newName );
+            final UserProfile otherProfile = findByLoginName( newName );
             if ( otherProfile != null )
             {
                 throw new DuplicateUserException( "security.error.cannot.rename", newName );
             }
         }
-        catch ( NoSuchPrincipalException e )
+        catch ( final NoSuchPrincipalException e )
         {
             // Good! That means it's safe to save using the new name
         }
 
         // Find the user with the old login id attribute, and change it
-        NodeList users = c_dom.getElementsByTagName( USER_TAG );
+        final NodeList users = c_dom.getElementsByTagName( USER_TAG );
         for( int i = 0; i < users.getLength(); i++ )
         {
-            Element user = (Element) users.item( i );
+            final Element user = (Element) users.item( i );
             if ( user.getAttribute( LOGIN_NAME ).equals( loginName ) )
             {
-            	DateFormat c_format = new SimpleDateFormat( DATE_FORMAT );
-                Date modDate = new Date( System.currentTimeMillis() );
+            	final DateFormat c_format = new SimpleDateFormat( DATE_FORMAT );
+                final Date modDate = new Date( System.currentTimeMillis() );
                 setAttribute( user, LOGIN_NAME, newName );
                 setAttribute( user, LAST_MODIFIED, c_format.format( modDate ) );
                 profile.setLoginName( newName );
@@ -511,7 +466,7 @@ public class XMLUserDatabase extends AbstractUserDatabase {
      * @param profile the user profile to save
      * @throws WikiSecurityException if the profile cannot be saved
      */
-    public synchronized void save( UserProfile profile ) throws WikiSecurityException
+    @Override public synchronized void save( final UserProfile profile ) throws WikiSecurityException
     {
         if ( c_dom == null )
         {
@@ -521,13 +476,13 @@ public class XMLUserDatabase extends AbstractUserDatabase {
 
         checkForRefresh();
 
-        DateFormat c_format = new SimpleDateFormat( DATE_FORMAT );
-        String index = profile.getLoginName();
-        NodeList users = c_dom.getElementsByTagName( USER_TAG );
+        final DateFormat c_format = new SimpleDateFormat( DATE_FORMAT );
+        final String index = profile.getLoginName();
+        final NodeList users = c_dom.getElementsByTagName( USER_TAG );
         Element user = null;
         for( int i = 0; i < users.getLength(); i++ )
         {
-            Element currentUser = (Element) users.item( i );
+            final Element currentUser = (Element) users.item( i );
             if ( currentUser.getAttribute( LOGIN_NAME ).equals( index ) )
             {
                 user = currentUser;
@@ -537,7 +492,7 @@ public class XMLUserDatabase extends AbstractUserDatabase {
 
         boolean isNew = false;
 
-        Date modDate = new Date( System.currentTimeMillis() );
+        final Date modDate = new Date( System.currentTimeMillis() );
         if( user == null )
         {
             // Create new user node
@@ -551,7 +506,7 @@ public class XMLUserDatabase extends AbstractUserDatabase {
         else
         {
             // To update existing user node, delete old attributes first...
-            NodeList attributes = user.getElementsByTagName( ATTRIBUTES_TAG );
+            final NodeList attributes = user.getElementsByTagName( ATTRIBUTES_TAG );
             for ( int i = 0; i < attributes.getLength(); i++ )
             {
                 user.removeChild( attributes.item( i ) );
@@ -564,14 +519,14 @@ public class XMLUserDatabase extends AbstractUserDatabase {
         setAttribute( user, FULL_NAME, profile.getFullname() );
         setAttribute( user, WIKI_NAME, profile.getWikiName() );
         setAttribute( user, EMAIL, profile.getEmail() );
-        Date lockExpiry = profile.getLockExpiry();
+        final Date lockExpiry = profile.getLockExpiry();
         setAttribute( user, LOCK_EXPIRY, lockExpiry == null ? "" : c_format.format( lockExpiry ) );
 
         // Hash and save the new password if it's different from old one
-        String newPassword = profile.getPassword();
+        final String newPassword = profile.getPassword();
         if ( newPassword != null && !newPassword.equals( "" ) )
         {
-            String oldPassword = user.getAttribute( PASSWORD );
+            final String oldPassword = user.getAttribute( PASSWORD );
             if ( !oldPassword.equals( newPassword ) )
             {
                 setAttribute( user, PASSWORD, getHash( newPassword ) );
@@ -583,13 +538,13 @@ public class XMLUserDatabase extends AbstractUserDatabase {
         {
             try
             {
-                String encodedAttributes = Serializer.serializeToBase64( profile.getAttributes() );
-                Element attributes = c_dom.createElement( ATTRIBUTES_TAG );
+                final String encodedAttributes = Serializer.serializeToBase64( profile.getAttributes() );
+                final Element attributes = c_dom.createElement( ATTRIBUTES_TAG );
                 user.appendChild( attributes );
-                Text value = c_dom.createTextNode( encodedAttributes );
+                final Text value = c_dom.createTextNode( encodedAttributes );
                 attributes.appendChild( value );
             }
-            catch ( IOException e )
+            catch ( final IOException e )
             {
                 throw new WikiSecurityException( "Could not save user profile attribute. Reason: " + e.getMessage(), e );
             }
@@ -614,7 +569,7 @@ public class XMLUserDatabase extends AbstractUserDatabase {
      * @param index
      * @return the profile, or <code>null</code> if not found
      */
-    private UserProfile findByAttribute( String matchAttribute, String index )
+    private UserProfile findByAttribute( final String matchAttribute, String index )
     {
         if ( c_dom == null )
         {
@@ -623,7 +578,7 @@ public class XMLUserDatabase extends AbstractUserDatabase {
 
         checkForRefresh();
 
-        NodeList users = c_dom.getElementsByTagName( USER_TAG );
+        final NodeList users = c_dom.getElementsByTagName( USER_TAG );
 
         if( users == null ) return null;
 
@@ -636,7 +591,7 @@ public class XMLUserDatabase extends AbstractUserDatabase {
 
         for( int i = 0; i < users.getLength(); i++ )
         {
-            Element user = (Element) users.item( i );
+            final Element user = (Element) users.item( i );
             String userAttribute = user.getAttribute( matchAttribute );
             if (!caseSensitiveCompare)
             {
@@ -645,7 +600,7 @@ public class XMLUserDatabase extends AbstractUserDatabase {
             }
             if ( userAttribute.equals( index ) )
             {
-                UserProfile profile = newProfile();
+                final UserProfile profile = newProfile();
 
                 // Parse basic attributes
                 profile.setUid( user.getAttribute( UID ) );
@@ -659,13 +614,13 @@ public class XMLUserDatabase extends AbstractUserDatabase {
                 profile.setEmail( user.getAttribute( EMAIL ) );
 
                 // Get created/modified timestamps
-                String created = user.getAttribute( CREATED );
-                String modified = user.getAttribute( LAST_MODIFIED );
+                final String created = user.getAttribute( CREATED );
+                final String modified = user.getAttribute( LAST_MODIFIED );
                 profile.setCreated( parseDate( profile, created ) );
                 profile.setLastModified( parseDate( profile, modified ) );
 
                 // Is the profile locked?
-                String lockExpiry = user.getAttribute( LOCK_EXPIRY );
+                final String lockExpiry = user.getAttribute( LOCK_EXPIRY );
                 if ( lockExpiry == null || lockExpiry.length() == 0 )
                 {
                     profile.setLockExpiry( null );
@@ -676,17 +631,17 @@ public class XMLUserDatabase extends AbstractUserDatabase {
                 }
 
                 // Extract all of the user's attributes (should only be one attributes tag, but you never know!)
-                NodeList attributes = user.getElementsByTagName( ATTRIBUTES_TAG );
+                final NodeList attributes = user.getElementsByTagName( ATTRIBUTES_TAG );
                 for ( int j = 0; j < attributes.getLength(); j++ )
                 {
-                    Element attribute = (Element)attributes.item( j );
-                    String serializedMap = extractText( attribute );
+                    final Element attribute = (Element)attributes.item( j );
+                    final String serializedMap = extractText( attribute );
                     try
                     {
-                        Map<String,? extends Serializable> map = Serializer.deserializeFromBase64( serializedMap );
+                        final Map<String,? extends Serializable> map = Serializer.deserializeFromBase64( serializedMap );
                         profile.getAttributes().putAll( map );
                     }
-                    catch ( IOException e )
+                    catch ( final IOException e )
                     {
                         log.error( "Could not parse user profile attributes!", e );
                     }
@@ -703,15 +658,15 @@ public class XMLUserDatabase extends AbstractUserDatabase {
      * @param element the base element
      * @return the text nodes that are immediate children of the base element, concatenated together
      */
-    private String extractText( Element element )
+    private String extractText( final Element element )
     {
         String text = "";
         if ( element.getChildNodes().getLength() > 0 )
         {
-            NodeList children = element.getChildNodes();
+            final NodeList children = element.getChildNodes();
             for ( int k = 0; k < children.getLength(); k++ )
             {
-                Node child = children.item( k );
+                final Node child = children.item( k );
                 if ( child.getNodeType() == Node.TEXT_NODE )
                 {
                     text = text + ((Text)child).getData();
@@ -729,20 +684,20 @@ public class XMLUserDatabase extends AbstractUserDatabase {
      *  @param date
      *  @return A parsed date, or null, if both parse attempts fail.
      */
-    private Date parseDate( UserProfile profile, String date )
+    private Date parseDate( final UserProfile profile, final String date )
     {
         try
         {
-        	DateFormat c_format = new SimpleDateFormat( DATE_FORMAT );
+        	final DateFormat c_format = new SimpleDateFormat( DATE_FORMAT );
             return c_format.parse( date );
         }
-        catch( ParseException e )
+        catch( final ParseException e )
         {
             try
             {
                 return DateFormat.getDateTimeInstance().parse( date );
             }
-            catch ( ParseException e2)
+            catch ( final ParseException e2)
             {
                 log.warn("Could not parse 'created' or 'lastModified' "
                     + "attribute for "
@@ -764,10 +719,10 @@ public class XMLUserDatabase extends AbstractUserDatabase {
             throw new IllegalStateException( "FATAL: database does not exist" );
         }
 
-        NodeList users = c_dom.getElementsByTagName( USER_TAG );
+        final NodeList users = c_dom.getElementsByTagName( USER_TAG );
         for( int i = 0; i < users.getLength(); i++ )
         {
-            Element user = (Element) users.item( i );
+            final Element user = (Element) users.item( i );
 
             // Sanitize UID (and generate a new one if one does not exist)
             String uid = user.getAttribute( UID ).trim();
@@ -778,10 +733,10 @@ public class XMLUserDatabase extends AbstractUserDatabase {
             }
 
             // Sanitize dates
-            String loginName = user.getAttribute( LOGIN_NAME );
+            final String loginName = user.getAttribute( LOGIN_NAME );
             String created = user.getAttribute( CREATED );
             String modified = user.getAttribute( LAST_MODIFIED );
-            DateFormat c_format = new SimpleDateFormat( DATE_FORMAT );
+            final DateFormat c_format = new SimpleDateFormat( DATE_FORMAT );
             try
             {
                 created = c_format.format( c_format.parse( created ) );
@@ -789,7 +744,7 @@ public class XMLUserDatabase extends AbstractUserDatabase {
                 user.setAttribute( CREATED,  created );
                 user.setAttribute( LAST_MODIFIED,  modified );
             }
-            catch( ParseException e )
+            catch( final ParseException e )
             {
                 try
                 {
@@ -798,7 +753,7 @@ public class XMLUserDatabase extends AbstractUserDatabase {
                     user.setAttribute( CREATED,  created );
                     user.setAttribute( LAST_MODIFIED,  modified );
                 }
-                catch ( ParseException e2 )
+                catch ( final ParseException e2 )
                 {
                     log.warn( "Could not parse 'created' or 'lastModified' attribute for profile '" + loginName + "'."
                             + " It may have been tampered with." );
@@ -813,7 +768,7 @@ public class XMLUserDatabase extends AbstractUserDatabase {
      * @param attribute the name of the attribute to set
      * @param value the desired attribute value
      */
-    private void setAttribute( Element element, String attribute, String value ) {
+    private void setAttribute( final Element element, final String attribute, final String value ) {
         if( value != null ) {
             element.setAttribute( attribute, value );
         }
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/tasks/DefaultTasksManager.java b/jspwiki-main/src/main/java/org/apache/wiki/tasks/DefaultTasksManager.java
index a0f2557..0ee74e5 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/tasks/DefaultTasksManager.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/tasks/DefaultTasksManager.java
@@ -18,15 +18,15 @@
  */
 package org.apache.wiki.tasks;
 
-import java.util.Locale;
-
 import org.apache.wiki.WikiContext;
-import org.apache.wiki.WikiEngine;
+import org.apache.wiki.api.core.Engine;
 import org.apache.wiki.tasks.auth.SaveUserProfileTask;
 import org.apache.wiki.tasks.pages.PreSaveWikiPageTask;
 import org.apache.wiki.tasks.pages.SaveWikiPageTask;
 import org.apache.wiki.workflow.Step;
 
+import java.util.Locale;
+
 
 /**
  * Default implementation for {@link TasksManager}.
@@ -37,7 +37,7 @@ public class DefaultTasksManager implements TasksManager {
      * {@inheritDoc}
      */
     @Override
-    public Step buildPreSaveWikiPageTask( WikiContext context, String proposedText ) {
+    public Step buildPreSaveWikiPageTask( final WikiContext context, final String proposedText ) {
         return new PreSaveWikiPageTask( context, proposedText );
     }
     
@@ -53,7 +53,7 @@ public class DefaultTasksManager implements TasksManager {
      * {@inheritDoc}
      */
     @Override
-    public Step buildSaveUserProfileTask( WikiEngine engine, Locale loc ) {
+    public Step buildSaveUserProfileTask( final Engine engine, final Locale loc ) {
         return new SaveUserProfileTask( engine, loc );
     }
     
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/tasks/TasksManager.java b/jspwiki-main/src/main/java/org/apache/wiki/tasks/TasksManager.java
index f2a7b61..c78774c 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/tasks/TasksManager.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/tasks/TasksManager.java
@@ -19,7 +19,7 @@
 package org.apache.wiki.tasks;
 
 import org.apache.wiki.WikiContext;
-import org.apache.wiki.WikiEngine;
+import org.apache.wiki.api.core.Engine;
 import org.apache.wiki.workflow.Step;
 
 import java.security.Principal;
@@ -64,6 +64,6 @@ public interface TasksManager {
      * @param loc text proposed to be saved on the wiki page.
      * @return a save user profile task.
      */
-    Step buildSaveUserProfileTask( WikiEngine engine, Locale loc );
+    Step buildSaveUserProfileTask( Engine engine, Locale loc );
     
 }
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/tasks/auth/SaveUserProfileTask.java b/jspwiki-main/src/main/java/org/apache/wiki/tasks/auth/SaveUserProfileTask.java
index 3ec7cb3..22f0f20 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/tasks/auth/SaveUserProfileTask.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/tasks/auth/SaveUserProfileTask.java
@@ -2,8 +2,9 @@ package org.apache.wiki.tasks.auth;
 
 import org.apache.log4j.Logger;
 import org.apache.wiki.WikiContext;
-import org.apache.wiki.WikiEngine;
+import org.apache.wiki.api.core.Engine;
 import org.apache.wiki.api.exceptions.WikiException;
+import org.apache.wiki.auth.UserManager;
 import org.apache.wiki.auth.user.UserProfile;
 import org.apache.wiki.i18n.InternationalizationManager;
 import org.apache.wiki.tasks.TasksManager;
@@ -24,14 +25,15 @@ public class SaveUserProfileTask extends Task {
 
     private static final long serialVersionUID = 6994297086560480285L;
     private static final Logger LOG = Logger.getLogger( SaveUserProfileTask.class );
-    private final WikiEngine m_engine;
+    private final Engine m_engine;
     private final Locale m_loc;
 
     /**
      * Constructs a new Task for saving a user profile.
+     *
      * @param engine the wiki engine
      */
-    public SaveUserProfileTask( final WikiEngine engine, final Locale loc ) {
+    public SaveUserProfileTask( final Engine engine, final Locale loc ) {
         super( TasksManager.USER_PROFILE_SAVE_TASK_MESSAGE_KEY );
         m_engine = engine;
         m_loc = loc;
@@ -39,8 +41,8 @@ public class SaveUserProfileTask extends Task {
 
     /**
      * Saves the user profile to the user database.
-     * @return {@link org.apache.wiki.workflow.Outcome#STEP_COMPLETE} if the
-     * task completed successfully
+     *
+     * @return {@link org.apache.wiki.workflow.Outcome#STEP_COMPLETE} if the task completed successfully
      * @throws WikiException if the save did not complete for some reason
      */
     @Override
@@ -49,12 +51,12 @@ public class SaveUserProfileTask extends Task {
         final UserProfile profile = ( UserProfile )getWorkflow().getAttribute( WorkflowManager.WF_UP_CREATE_SAVE_ATTR_SAVED_PROFILE );
 
         // Save the profile (userdatabase will take care of timestamps for us)
-        m_engine.getUserManager().getUserDatabase().save( profile );
+        m_engine.getManager( UserManager.class ).getUserDatabase().save( profile );
 
         // Send e-mail if user supplied an e-mail address
-        if ( profile.getEmail() != null ) {
+        if ( profile != null && profile.getEmail() != null ) {
             try {
-                final InternationalizationManager i18n = m_engine.getInternationalizationManager();
+                final InternationalizationManager i18n = m_engine.getManager( InternationalizationManager.class );
                 final String app = m_engine.getApplicationName();
                 final String to = profile.getEmail();
                 final String subject = i18n.get( InternationalizationManager.DEF_TEMPLATE, m_loc,
diff --git a/jspwiki-main/src/main/java/org/apache/wiki/workflow/WorkflowBuilder.java b/jspwiki-main/src/main/java/org/apache/wiki/workflow/WorkflowBuilder.java
index 4aa3de8..c18b8b8 100644
--- a/jspwiki-main/src/main/java/org/apache/wiki/workflow/WorkflowBuilder.java
+++ b/jspwiki-main/src/main/java/org/apache/wiki/workflow/WorkflowBuilder.java
@@ -18,26 +18,26 @@
  */
 package org.apache.wiki.workflow;
 
+import org.apache.wiki.api.core.Engine;
+import org.apache.wiki.api.exceptions.WikiException;
+
 import java.security.Principal;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
-import org.apache.wiki.WikiEngine;
-import org.apache.wiki.api.exceptions.WikiException;
-
 /**
  * Factory class that creates common Workflow instances such as a standard approval workflow.
  */
-public final class WorkflowBuilder
-{
-    private static final Map<WikiEngine, WorkflowBuilder> BUILDERS = new ConcurrentHashMap<>();
-    private final WikiEngine m_engine;
+public final class WorkflowBuilder {
+
+    private static final Map< Engine, WorkflowBuilder > BUILDERS = new ConcurrentHashMap<>();
+    private final Engine m_engine;
 
     /**
      * Private constructor that creates a new WorkflowBuilder for the supplied WikiEngine.
      * @param engine the wiki engine
      */
-    private WorkflowBuilder( WikiEngine engine )
+    private WorkflowBuilder( final Engine engine )
     {
         m_engine = engine;
     }
@@ -48,11 +48,9 @@ public final class WorkflowBuilder
      * @param engine the wiki engine
      * @return the workflow builder
      */
-    public static WorkflowBuilder getBuilder( WikiEngine engine )
-    {
+    public static WorkflowBuilder getBuilder( final Engine engine ) {
         WorkflowBuilder builder = BUILDERS.get( engine );
-        if ( builder == null )
-        {
+        if ( builder == null ) {
             builder = new WorkflowBuilder( engine );
             BUILDERS.put( engine, builder );
         }
@@ -102,46 +100,40 @@ public final class WorkflowBuilder
      * @return the created workflow
      * @throws WikiException if the name of the approving user, Role or Group cannot be determined
      */
-    public Workflow buildApprovalWorkflow( Principal submitter,
-                                           String workflowApproverKey,
-                                           Step prepTask,
-                                           String decisionKey,
-                                           Fact[] facts,
-                                           Step completionTask,
-                                           String rejectedMessageKey ) throws WikiException
-    {
-        WorkflowManager mgr = m_engine.getWorkflowManager();
-        Workflow workflow = new Workflow( workflowApproverKey, submitter );
+    public Workflow buildApprovalWorkflow( final Principal submitter,
+                                           final String workflowApproverKey,
+                                           final Step prepTask,
+                                           final String decisionKey,
+                                           final Fact[] facts,
+                                           final Step completionTask,
+                                           final String rejectedMessageKey ) throws WikiException {
+        final WorkflowManager mgr = m_engine.getManager( WorkflowManager.class );
+        final Workflow workflow = new Workflow( workflowApproverKey, submitter );
 
         // Is a Decision required to run the approve task?
-        boolean decisionRequired = mgr.requiresApproval( workflowApproverKey );
+        final boolean decisionRequired = mgr.requiresApproval( workflowApproverKey );
 
         // If Decision required, create a simple approval workflow
-        if ( decisionRequired )
-        {
+        if ( decisionRequired ) {
             // Look up the name of the approver (user or group) listed in jspwiki.properties;
             // approvals go to the approver's decision cue
-            Principal approverPrincipal = mgr.getApprover( workflowApproverKey );
-            Decision decision = new SimpleDecision( workflow, decisionKey, approverPrincipal );
+            final Principal approverPrincipal = mgr.getApprover( workflowApproverKey );
+            final Decision decision = new SimpleDecision( workflow, decisionKey, approverPrincipal );
 
             // Add facts to the Decision, if any were supplied
-            if ( facts != null )
-            {
-                for ( Fact fact: facts )
-                {
+            if( facts != null ) {
+                for( final Fact fact : facts ) {
                     decision.addFact( fact );
                 }
                 // Add the first one as a message key
-                if ( facts.length > 0 )
-                {
-                    workflow.addMessageArgument( facts[0].getValue() );
+                if( facts.length > 0 ) {
+                    workflow.addMessageArgument( facts[ 0 ].getValue() );
                 }
             }
 
             // If rejected, sent a notification
-            if ( rejectedMessageKey != null )
-            {
-                SimpleNotification rejectNotification = new SimpleNotification( workflow, rejectedMessageKey, submitter );
+            if ( rejectedMessageKey != null ) {
+                final SimpleNotification rejectNotification = new SimpleNotification( workflow, rejectedMessageKey, submitter );
                 decision.addSuccessor( Outcome.DECISION_DENY, rejectNotification );
             }
 
@@ -149,35 +141,24 @@ public final class WorkflowBuilder
             decision.addSuccessor( Outcome.DECISION_APPROVE, completionTask );
 
             // Set the first step
-            if ( prepTask == null )
-            {
+            if( prepTask == null ) {
                 workflow.setFirstStep( decision );
-            }
-            else
-            {
+            } else {
                 workflow.setFirstStep( prepTask );
                 prepTask.addSuccessor( Outcome.STEP_COMPLETE, decision );
             }
-        }
-
-        // If Decision not required, just run the prep + approved tasks in succession
-        else
-        {
+        } else { // If Decision not required, just run the prep + approved tasks in succession
             // Set the first step
-            if ( prepTask == null )
-            {
+            if ( prepTask == null ) {
                 workflow.setFirstStep( completionTask );
-            }
-            else
-            {
+            } else {
                 workflow.setFirstStep( prepTask );
                 prepTask.addSuccessor( Outcome.STEP_COMPLETE, completionTask );
             }
         }
 
         // Make sure our tasks have this workflow as the parent, then return
-        if ( prepTask != null )
-        {
+        if( prepTask != null ) {
             prepTask.setWorkflow( workflow );
         }
         completionTask.setWorkflow( workflow );
diff --git a/jspwiki-main/src/main/resources/ini/classmappings.xml b/jspwiki-main/src/main/resources/ini/classmappings.xml
index b419955..d572965 100644
--- a/jspwiki-main/src/main/resources/ini/classmappings.xml
+++ b/jspwiki-main/src/main/resources/ini/classmappings.xml
@@ -77,7 +77,7 @@
   </mapping>
   <mapping>
     <requestedClass>org.apache.wiki.auth.UserManager</requestedClass>
-    <mappedClass>org.apache.wiki.auth.UserManager</mappedClass>
+    <mappedClass>org.apache.wiki.auth.DefaultUserManager</mappedClass>
   </mapping>
   <mapping>
     <requestedClass>org.apache.wiki.auth.acl.AclManager</requestedClass>