You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2015/07/09 18:29:35 UTC

[6/9] isis git commit: ISIS-1169: change to (try to) avoid "session already open" error (http://isis.markmail.org/thread/2dn7tja3r466yd2m)

ISIS-1169: change to (try to) avoid "session already open" error (http://isis.markmail.org/thread/2dn7tja3r466yd2m)

- IsisContextThreadLocal now uses AUTO_CLOSE session policy instead of EXPLICIT_CLOSE session policy
- simplified IsisSessionFilter:
  - always call closeSession() in a finally block
  - get rid of SessionState enum, since never used
- enhanced IsisTransactionFilterForRestfulObjects to be resilient of the fact that the session/transaction may no longer exist (if the authSession is closed by a resource)
- added user/logout resource to UserResourceServerSide, enhanced UserReprRenderer to display new link (plus new proprietary Rel for this)
- extended IsisSessionFilter, also AuthenticationSessionStrategy interface
  - special query string to set on redirect to request that the session be closed
  - new AuthenticationSessionStrategy#invalidate(), called on this
   - default impl returns 401, UNAUTHORIZED... this is enough to effect a server-side "logout" for BASIC auth
- updated IsisSession#close() so that if takeSnapshot() throws exception, then this doesn't prevent the persistence session from being closed
- updated PersistenceSession#close(), better debugging if the objectStore#close() or the adapterManager#close() were to throw an exception.
- removed unused DeploymentType#UTILITY instance.


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/edc4fa76
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/edc4fa76
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/edc4fa76

Branch: refs/heads/master
Commit: edc4fa7648f73dea2c3be41de24b29ca76af9fe4
Parents: 7db0239
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Thu Jul 9 11:25:50 2015 +0100
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Thu Jul 9 11:29:57 2015 +0100

----------------------------------------------------------------------
 .../core/runtime/system/DeploymentType.java     |   1 -
 .../runtime/system/context/IsisContext.java     |  14 +-
 .../system/context/IsisContextThreadLocal.java  |   6 +-
 .../system/persistence/PersistenceSession.java  |  29 +--
 .../runtime/system/session/IsisSession.java     |   5 -
 .../system/session/IsisSessionDefault.java      |  12 +-
 .../isis/core/webapp/IsisSessionFilter.java     | 205 +++++++------------
 .../auth/AuthenticationSessionStrategy.java     |  10 +-
 .../AuthenticationSessionStrategyAbstract.java  |  12 +-
 .../AuthenticationSessionStrategyDefault.java   |  28 ++-
 .../isis/viewer/restfulobjects/applib/Rel.java  |   3 +-
 .../applib/user/UserResource.java               |  19 +-
 .../rendering/RendererContext.java              |   2 +-
 .../AuthenticationSessionStrategyBasicAuth.java |  39 ++--
 .../AuthenticationSessionStrategyHeader.java    |  10 +-
 .../AuthenticationSessionStrategyTrusted.java   |   8 +-
 .../server/resources/ResourceAbstract.java      |  12 +-
 .../server/resources/UserReprRenderer.java      |   7 +
 .../resources/UserResourceServerside.java       |  28 +++
 .../IsisTransactionFilterForRestfulObjects.java |  10 +-
 20 files changed, 257 insertions(+), 203 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/edc4fa76/core/runtime/src/main/java/org/apache/isis/core/runtime/system/DeploymentType.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/DeploymentType.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/DeploymentType.java
index e92b5ff..3cde2b1 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/DeploymentType.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/DeploymentType.java
@@ -62,7 +62,6 @@ public class DeploymentType implements DeploymentCategoryProvider {
     public static DeploymentType SERVER_EXPLORATION = new DeploymentType("SERVER_EXPLORATION", DeploymentCategory.EXPLORING, ContextCategory.THREADLOCAL);
     public static DeploymentType SERVER_PROTOTYPE = new DeploymentType("SERVER_PROTOTYPE", DeploymentCategory.PROTOTYPING, ContextCategory.THREADLOCAL);
     public static DeploymentType UNIT_TESTING = new DeploymentType("UNIT_TESTING", DeploymentCategory.PRODUCTION, ContextCategory.STATIC_RELAXED);
-    public static DeploymentType UTILITY = new DeploymentType("UTILITY", DeploymentCategory.EXPLORING, ContextCategory.STATIC);
 
     /**
      * Look up {@link DeploymentType} by their {@link #name()}.

http://git-wip-us.apache.org/repos/asf/isis/blob/edc4fa76/core/runtime/src/main/java/org/apache/isis/core/runtime/system/context/IsisContext.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/context/IsisContext.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/context/IsisContext.java
index e078665..287c282 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/context/IsisContext.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/context/IsisContext.java
@@ -95,7 +95,10 @@ public abstract class IsisContext implements DebuggableWithTitle {
     protected static enum SessionClosePolicy {
         /**
          * Sessions must be explicitly closed.
+         *
+         * @deprecated - in 1.9.0-SNAPSHOT it has been reported that on occasion the session is not explicitly closed.  This must mean that there's a leakage somewhere.  Using auto close instead will make the system overall more able to "repair itself" when this type of error (presumably a bug in our session management code) occurs
          */
+        @Deprecated
         EXPLICIT_CLOSE,
         /**
          * Sessions will be automatically closed.
@@ -211,8 +214,9 @@ public abstract class IsisContext implements DebuggableWithTitle {
      * threads have finished with a session can it really be closed.
      */
     public void closeSessionInstance() {
-        if (getSessionInstance() != null) {
-            getSessionInstance().close();
+        final IsisSession isisSession = getSessionInstance();
+        if (isisSession != null) {
+            isisSession.close();
             doClose();
         }
     }
@@ -445,7 +449,11 @@ public abstract class IsisContext implements DebuggableWithTitle {
     // ///////////////////////////////////////////////////////////
 
     public static boolean inTransaction() {
-        return inSession() && getCurrentTransaction() != null && !getCurrentTransaction().getState().isComplete();
+        if (inSession())
+            if (getCurrentTransaction() != null)
+                if (!getCurrentTransaction().getState().isComplete())
+                    return true;
+        return false;
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/isis/blob/edc4fa76/core/runtime/src/main/java/org/apache/isis/core/runtime/system/context/IsisContextThreadLocal.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/context/IsisContextThreadLocal.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/context/IsisContextThreadLocal.java
index 5c34916..e99e485 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/context/IsisContextThreadLocal.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/context/IsisContextThreadLocal.java
@@ -42,6 +42,7 @@ public class IsisContextThreadLocal extends IsisContext {
         return new IsisContextThreadLocal(sessionFactory);
     }
 
+    // TODO: could convert this to a regular ThreadLocal, I think; except for the closeAllSessionsInstance() method...; is that method really needed?
     private final Map<Thread, IsisSession> sessionsByThread = new IdentityHashMap<>();
 
     
@@ -50,7 +51,7 @@ public class IsisContextThreadLocal extends IsisContext {
     // //////////////////////////////////////////////
 
     protected IsisContextThreadLocal(final IsisSessionFactory sessionFactory) {
-        this(ContextReplacePolicy.NOT_REPLACEABLE, SessionClosePolicy.EXPLICIT_CLOSE, sessionFactory);
+        this(ContextReplacePolicy.NOT_REPLACEABLE, SessionClosePolicy.AUTO_CLOSE, sessionFactory);
     }
 
     protected IsisContextThreadLocal(final ContextReplacePolicy contextReplacePolicy, final SessionClosePolicy sessionClosePolicy, final IsisSessionFactory sessionFactory) {
@@ -184,8 +185,7 @@ public class IsisContextThreadLocal extends IsisContext {
     @Override
     public IsisSession getSessionInstance() {
         final Thread thread = Thread.currentThread();
-        final IsisSession session = sessionsByThread.get(thread);
-        return session;
+        return sessionsByThread.get(thread);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/edc4fa76/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/PersistenceSession.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/PersistenceSession.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/PersistenceSession.java
index 5c64197..d5e01ec 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/PersistenceSession.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/persistence/PersistenceSession.java
@@ -238,28 +238,31 @@ public class PersistenceSession implements SessionScopedComponent, DebuggableWit
      */
     @Override
     public void close() {
+
         if (getState() == State.CLOSED) {
             // nothing to do
             return;
         }
 
-        if (LOG.isDebugEnabled()) {
-            LOG.debug("closing " + this);
-        }
-        
         try {
-            objectStore.close();
-        } catch(final RuntimeException ex) {
-            // ignore
-        }
+            try {
+                objectStore.close();
+            } catch(final Throwable ex) {
+                // ignore
+                LOG.error("objectStore#close() failed while closing the session; continuing to avoid memory leakage");
+            }
 
-        try {
-            adapterManager.close();
-        } catch(final RuntimeException ex) {
-            // ignore
+            try {
+                adapterManager.close();
+            } catch(final Throwable ex) {
+                // ignore
+                LOG.error("adapterManager#close() failed while closing the session; continuing to avoid memory leakage");
+            }
+
+        } finally {
+            setState(State.CLOSED);
         }
 
-        setState(State.CLOSED);
     }
 
 

http://git-wip-us.apache.org/repos/asf/isis/blob/edc4fa76/core/runtime/src/main/java/org/apache/isis/core/runtime/system/session/IsisSession.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/session/IsisSession.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/session/IsisSession.java
index 3c95e12..0f42039 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/session/IsisSession.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/session/IsisSession.java
@@ -74,11 +74,6 @@ public interface IsisSession extends SessionScopedComponent {
 
     /**
      * The {@link PersistenceSession} within this {@link IsisSession}.
-     * 
-     * <p>
-     * Would have been created by the {@link #getSessionFactory() owning
-     * factory}'s
-     * 
      */
     PersistenceSession getPersistenceSession();
 

http://git-wip-us.apache.org/repos/asf/isis/blob/edc4fa76/core/runtime/src/main/java/org/apache/isis/core/runtime/system/session/IsisSessionDefault.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/session/IsisSessionDefault.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/session/IsisSessionDefault.java
index c74a4b8..4dcb7b3 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/system/session/IsisSessionDefault.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/system/session/IsisSessionDefault.java
@@ -102,8 +102,16 @@ public class IsisSessionDefault implements IsisSession {
      */
     @Override
     public void close() {
-        takeSnapshot();
-        getPersistenceSession().close();
+        try {
+            takeSnapshot();
+        } catch(Throwable ex) {
+            LOG.error("Failed to takeSnapshot while closing the session; continuing to avoid memory leakage");
+        }
+
+        final PersistenceSession persistenceSession = getPersistenceSession();
+        if(persistenceSession != null) {
+            persistenceSession.close();
+        }
     }
 
     // //////////////////////////////////////////////////////

http://git-wip-us.apache.org/repos/asf/isis/blob/edc4fa76/core/runtime/src/main/java/org/apache/isis/core/webapp/IsisSessionFilter.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/webapp/IsisSessionFilter.java b/core/runtime/src/main/java/org/apache/isis/core/webapp/IsisSessionFilter.java
index 21680e6..b1d50d0 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/webapp/IsisSessionFilter.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/webapp/IsisSessionFilter.java
@@ -59,9 +59,7 @@ public class IsisSessionFilter implements Filter {
     public static final String AUTHENTICATION_SESSION_STRATEGY_KEY = "authenticationSessionStrategy";
 
     /**
-     * Default value for
-     * {@link AuthenticationSessionLookupStrategyConstants#AUTHENTICATION_SESSION_STRATEGY_KEY}
-     * if not specified.
+     * Default value for {@link #AUTHENTICATION_SESSION_STRATEGY_KEY} if not specified.
      */
     public static final String AUTHENTICATION_SESSION_STRATEGY_DEFAULT = AuthenticationSessionStrategyDefault.class.getName();
 
@@ -115,11 +113,7 @@ public class IsisSessionFilter implements Filter {
      * accomplishes the same thing).
      * 
      * <p>
-     * The value is expected as a comma separated list, for example:
-     * 
-     * <pre>
-     * htmlviewer
-     * </pre>
+     * The value is expected as a comma separated list.
      */
     public static final String IGNORE_EXTENSIONS_KEY = "ignoreExtensions";
 
@@ -131,6 +125,11 @@ public class IsisSessionFilter implements Filter {
 
     };
 
+    /**
+     * Somewhat hacky, add this to the query
+     */
+    public static final String QUERY_STRING_FORCE_LOGOUT = "__isis_force_logout";
+
     static void redirect(final HttpServletRequest httpRequest, final HttpServletResponse httpResponse, final String redirectTo) throws IOException {
         httpResponse.sendRedirect(StringExtensions.combinePath(httpRequest.getContextPath(), redirectTo));
     }
@@ -193,17 +192,10 @@ public class IsisSessionFilter implements Filter {
         public abstract void handle(IsisSessionFilter filter, HttpServletRequest httpRequest, HttpServletResponse httpResponse, FilterChain chain) throws IOException, ServletException;
     }
 
-    /**
-     * Used as a flag on {@link HttpServletRequest} so that if there are two
-     * {@link IsisSessionFilter}s in a request pipeline, then the second can
-     * determine what processing has already been done (and is usually then just
-     * a no-op).
-     */
-    private static final String SESSION_STATE_KEY = IsisSessionFilter.SessionState.class.getName();
 
     private AuthenticationSessionStrategy authSessionStrategy;
     private List<String> restrictedPaths;
-    private WhenNoSession whenNoSession;
+    private WhenNoSession whenNotAuthenticated;
     private String redirectToOnException;
     private Collection<Pattern> ignoreExtensions;
 
@@ -237,18 +229,22 @@ public class IsisSessionFilter implements Filter {
         final String logonPage = config.getInitParameter(LOGON_PAGE_KEY);
         if (logonPage != null) {
             if (whenNoSessionStr != null) {
-                throw new IllegalStateException("The init-param '" + LOGON_PAGE_KEY + "' is only provided for backwards compatibility; remove if the init-param '" + WHEN_NO_SESSION_KEY + "' has been specified");
+                throw new IllegalStateException(String.format(
+                        "The init-param '%s' is only provided for backwards compatibility; "
+                        + "remove if the init-param '%s' has been specified", LOGON_PAGE_KEY, WHEN_NO_SESSION_KEY));
+            } else {
+                // default whenNotAuthenticated and allow access through to the logonPage
+                whenNotAuthenticated = WhenNoSession.RESTRICTED;
+                this.restrictedPaths = Lists.newArrayList(logonPage);
+                return;
             }
-            whenNoSession = WhenNoSession.RESTRICTED;
-            this.restrictedPaths = Lists.newArrayList(logonPage);
-            return;
         }
 
-        whenNoSession = WhenNoSession.lookup(whenNoSessionStr);
-        if (whenNoSession == WhenNoSession.RESTRICTED) {
+        whenNotAuthenticated = WhenNoSession.lookup(whenNoSessionStr);
+        if (whenNotAuthenticated == WhenNoSession.RESTRICTED) {
             final String restrictedPathsStr = config.getInitParameter(RESTRICTED_KEY);
             if (restrictedPathsStr == null) {
-                throw new IllegalStateException("Require an init-param of '" + RESTRICTED_KEY + "' key to be set.");
+                throw new IllegalStateException(String.format("Require an init-param of '%s' key to be set.", RESTRICTED_KEY));
             }
             this.restrictedPaths = Lists.newArrayList(Splitter.on(",").split(restrictedPathsStr));
         }
@@ -281,137 +277,80 @@ public class IsisSessionFilter implements Filter {
     // doFilter
     // /////////////////////////////////////////////////////////////////
 
-    public enum SessionState {
-
-        UNDEFINED {
-            @Override
-            public void handle(final IsisSessionFilter filter, final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
-
-                final HttpServletRequest httpRequest = (HttpServletRequest) request;
-                final HttpServletResponse httpResponse = (HttpServletResponse) response;
-
-                if (requestIsIgnoreExtension(filter, httpRequest)) {
-                    try {
-                        chain.doFilter(request, response);
-                        return;
-                    } finally {
-                        closeSession();
-                    }
-                }
+    @Override
+    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
 
-                if (ResourceCachingFilter.isCachedResource(httpRequest)) {
-                    try {
-                        chain.doFilter(request, response);
-                        return;
-                    } finally {
-                        closeSession();
-                    }
-                }
+        final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
+        final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
 
-                // authenticate
-                final AuthenticationSession validSession = filter.authSessionStrategy.lookupValid(request, response);
-                if (validSession != null) {
-                    filter.authSessionStrategy.bind(request, response, validSession);
+        try {
+            final String queryString = httpServletRequest.getQueryString();
+            if (queryString != null && queryString.contains(QUERY_STRING_FORCE_LOGOUT)) {
 
-                    openSession(validSession);
-                    SESSION_IN_PROGRESS.setOn(request);
+                authSessionStrategy.invalidate(httpServletRequest, httpServletResponse);
+                return;
+            }
 
-                    try {
-                        chain.doFilter(request, response);
-                    } finally {
-                        UNDEFINED.setOn(request);
-                        closeSession();
-                    }
-                    return;
-                }
+            if (requestIsIgnoreExtension(this, httpServletRequest) ||
+                ResourceCachingFilter.isCachedResource(httpServletRequest)) {
+                chain.doFilter(request, response);
+                return;
+            }
 
-                try {
-                    NO_SESSION_SINCE_NOT_AUTHENTICATED.setOn(request);
-                    filter.whenNoSession.handle(filter, httpRequest, httpResponse, chain);
-                } catch (final RuntimeException ex) {
-                    // in case the destination servlet cannot cope, but we've
-                    // been told
-                    // to redirect elsewhere
-                    if (filter.redirectToOnException != null) {
-                        redirect(httpRequest, httpResponse, filter.redirectToOnException);
-                        return;
-                    }
-                    throw ex;
-                } catch (final IOException ex) {
-                    if (filter.redirectToOnException != null) {
-                        redirect(httpRequest, httpResponse, filter.redirectToOnException);
-                        return;
-                    }
-                    throw ex;
-                } catch (final ServletException ex) {
-                    // in case the destination servlet cannot cope, but we've
-                    // been told
-                    // to redirect elsewhere
-                    if (filter.redirectToOnException != null) {
-                        redirect(httpRequest, httpResponse, filter.redirectToOnException);
-                        return;
-                    }
-                    throw ex;
-                } finally {
-                    UNDEFINED.setOn(request);
-                    // nothing to do
-                }
+            // authenticate
+            final AuthenticationSession authSession =
+                    authSessionStrategy.lookupValid(httpServletRequest, httpServletResponse);
+            if (authSession != null) {
+                authSessionStrategy.bind(httpServletRequest, httpServletResponse, authSession);
 
+                openSession(authSession); // is closed in the finally block
+                chain.doFilter(request, response);
+                return;
             }
 
-            private boolean requestIsIgnoreExtension(final IsisSessionFilter filter, final HttpServletRequest httpRequest) {
-                final String servletPath = httpRequest.getServletPath();
-                for (final Pattern extension : filter.ignoreExtensions) {
-                    if (extension.matcher(servletPath).matches()) {
-                        return true;
-                    }
+            try {
+                whenNotAuthenticated.handle(this, httpServletRequest, httpServletResponse, chain);
+            } catch (final RuntimeException | IOException | ServletException ex) {
+                // in case the destination servlet cannot cope, but we've
+                // been told to redirect elsewhere
+                if (redirectToOnException != null) {
+                    redirect(httpServletRequest, httpServletResponse, redirectToOnException);
+                    return;
                 }
-                return false;
+                throw ex;
             }
-        },
-        NO_SESSION_SINCE_REDIRECTING_TO_LOGON_PAGE, NO_SESSION_SINCE_NOT_AUTHENTICATED, SESSION_IN_PROGRESS;
-
-        static SessionState lookup(final ServletRequest request) {
-            final Object state = request.getAttribute(SESSION_STATE_KEY);
-            return state != null ? (SessionState) state : SessionState.UNDEFINED;
-        }
-
-        boolean isValid(final AuthenticationSession authSession) {
-            return authSession != null && getAuthenticationManager().isSessionValid(authSession);
-        }
-
-        void setOn(final ServletRequest request) {
-            request.setAttribute(SESSION_STATE_KEY, this);
-        }
 
-        public void handle(final IsisSessionFilter filter, final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
-            chain.doFilter(request, response);
+        } finally {
+            closeSession();
         }
 
-        AuthenticationManager getAuthenticationManager() {
-            return IsisContext.getAuthenticationManager();
-        }
-
-        IsisSession openSession(final AuthenticationSession authSession) {
-            return IsisContext.openSession(authSession);
-        }
+    }
 
-        void closeSession() {
-            IsisContext.closeSession();
+    private boolean requestIsIgnoreExtension(final IsisSessionFilter filter, final HttpServletRequest httpRequest) {
+        final String servletPath = httpRequest.getServletPath();
+        for (final Pattern extension : filter.ignoreExtensions) {
+            if (extension.matcher(servletPath).matches()) {
+                return true;
+            }
         }
+        return false;
+    }
 
+    protected IsisTransactionManager getTransactionManager() {
+        return IsisContext.getTransactionManager();
     }
 
-    @Override
-    public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException {
+    private AuthenticationManager getAuthenticationManager() {
+        return IsisContext.getAuthenticationManager();
+    }
 
-        final SessionState sessionState = SessionState.lookup(request);
-        sessionState.handle(this, request, response, chain);
+    private IsisSession openSession(final AuthenticationSession authSession) {
+        return IsisContext.openSession(authSession);
     }
 
-    
-    protected IsisTransactionManager getTransactionManager() {
-        return IsisContext.getTransactionManager();
+    private void closeSession() {
+        IsisContext.closeSession();
     }
 
+
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/edc4fa76/core/runtime/src/main/java/org/apache/isis/core/webapp/auth/AuthenticationSessionStrategy.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/webapp/auth/AuthenticationSessionStrategy.java b/core/runtime/src/main/java/org/apache/isis/core/webapp/auth/AuthenticationSessionStrategy.java
index b0f5bd4..b95bec8 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/webapp/auth/AuthenticationSessionStrategy.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/webapp/auth/AuthenticationSessionStrategy.java
@@ -19,8 +19,8 @@
 
 package org.apache.isis.core.webapp.auth;
 
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
 import org.apache.isis.core.commons.authentication.AuthenticationSession;
 import org.apache.isis.core.runtime.authentication.AuthenticationManager;
@@ -37,7 +37,9 @@ public interface AuthenticationSessionStrategy {
      * {@link AuthenticationManager#isSessionValid(AuthenticationSession)
      * still-valid} {@link AuthenticationSession}.
      */
-    AuthenticationSession lookupValid(ServletRequest servletRequest, ServletResponse servletResponse);
+    AuthenticationSession lookupValid(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse);
 
-    void bind(ServletRequest servletRequest, ServletResponse servletResponse, AuthenticationSession authSession);
+    void bind(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse, final AuthenticationSession authSession);
+
+    void invalidate(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse);
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/edc4fa76/core/runtime/src/main/java/org/apache/isis/core/webapp/auth/AuthenticationSessionStrategyAbstract.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/webapp/auth/AuthenticationSessionStrategyAbstract.java b/core/runtime/src/main/java/org/apache/isis/core/webapp/auth/AuthenticationSessionStrategyAbstract.java
index 142340f..6f01b75 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/webapp/auth/AuthenticationSessionStrategyAbstract.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/webapp/auth/AuthenticationSessionStrategyAbstract.java
@@ -20,14 +20,16 @@ package org.apache.isis.core.webapp.auth;
 
 import javax.servlet.ServletContext;
 import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
 import org.apache.isis.core.commons.authentication.AuthenticationSession;
 
 public abstract class AuthenticationSessionStrategyAbstract implements AuthenticationSessionStrategy {
 
+    public static final int STATUS_UNAUTHORIZED = 401;
+
     protected HttpSession getHttpSession(final ServletRequest servletRequest) {
         final HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
         return httpServletRequest.getSession();
@@ -39,8 +41,14 @@ public abstract class AuthenticationSessionStrategyAbstract implements Authentic
     }
 
     @Override
-    public void bind(final ServletRequest servletRequest, final ServletResponse servletResponse, final AuthenticationSession authSession) {
+    public void bind(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse, final AuthenticationSession authSession) {
         // no-op
     }
 
+    @Override
+    public void invalidate(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse) {
+        bind(httpServletRequest, httpServletResponse, null);
+        httpServletResponse.setStatus(STATUS_UNAUTHORIZED);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/edc4fa76/core/runtime/src/main/java/org/apache/isis/core/webapp/auth/AuthenticationSessionStrategyDefault.java
----------------------------------------------------------------------
diff --git a/core/runtime/src/main/java/org/apache/isis/core/webapp/auth/AuthenticationSessionStrategyDefault.java b/core/runtime/src/main/java/org/apache/isis/core/webapp/auth/AuthenticationSessionStrategyDefault.java
index cf884ee..5d8350a 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/webapp/auth/AuthenticationSessionStrategyDefault.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/webapp/auth/AuthenticationSessionStrategyDefault.java
@@ -20,8 +20,8 @@
 package org.apache.isis.core.webapp.auth;
 
 import javax.servlet.ServletContext;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
 import org.apache.isis.applib.fixtures.LogonFixture;
@@ -42,13 +42,13 @@ import org.apache.isis.core.webapp.WebAppConstants;
  * The session is looked-up as follows:
  * <ul>
  * <li>it looks up from the {@link HttpSession} using the value
- * {@value WebAppConstants#HTTP_SESSION_AUTHENTICATION_SESSION_KEY}</li>
+ * {@link WebAppConstants#HTTP_SESSION_AUTHENTICATION_SESSION_KEY}</li>
  * <li>failing that, if in exploration mode, then returns an exploration session
  * </li>
  * <li>failing that, if a {@link LogonFixture} has been provided and not already
  * used, will provide an session for that fixture. The {@link HttpSession} also
  * stores the value
- * {@value WebAppConstants#HTTP_SESSION_LOGGED_ON_PREVIOUSLY_USING_LOGON_FIXTURE_KEY}
+ * {@link WebAppConstants#HTTP_SESSION_LOGGED_ON_PREVIOUSLY_USING_LOGON_FIXTURE_KEY}
  * in the session to track whether this has been done</li>
  * </ul>
  * <p>
@@ -56,10 +56,10 @@ import org.apache.isis.core.webapp.WebAppConstants;
 public class AuthenticationSessionStrategyDefault extends AuthenticationSessionStrategyAbstract {
 
     @Override
-    public AuthenticationSession lookupValid(final ServletRequest servletRequest, final ServletResponse servletResponse) {
+    public AuthenticationSession lookupValid(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse) {
 
         final AuthenticationManager authenticationManager = getAuthenticationManager();
-        final HttpSession httpSession = getHttpSession(servletRequest);
+        final HttpSession httpSession = getHttpSession(httpServletRequest);
 
         // use previously authenticated session if available
         AuthenticationSession authSession = (AuthenticationSession) httpSession.getAttribute(WebAppConstants.HTTP_SESSION_AUTHENTICATION_SESSION_KEY);
@@ -71,7 +71,7 @@ public class AuthenticationSessionStrategyDefault extends AuthenticationSessionS
         }
 
         // otherwise, look for LogonFixture and try to authenticate
-        final ServletContext servletContext = getServletContext(servletRequest);
+        final ServletContext servletContext = getServletContext(httpServletRequest);
         final IsisSystem system = (IsisSystem) servletContext.getAttribute(WebAppConstants.ISIS_SYSTEM_KEY);
         if (system == null) {
             // not expected to happen...
@@ -97,11 +97,19 @@ public class AuthenticationSessionStrategyDefault extends AuthenticationSessionS
     }
 
     @Override
-    public void bind(final ServletRequest servletRequest, final ServletResponse servletResponse, final AuthenticationSession authSession) {
-        final HttpSession httpSession = getHttpSession(servletRequest);
-        httpSession.setAttribute(WebAppConstants.HTTP_SESSION_AUTHENTICATION_SESSION_KEY, authSession);
+    public void bind(
+            final HttpServletRequest httpServletRequest,
+            final HttpServletResponse httpServletResponse,
+            final AuthenticationSession authSession) {
+        final HttpSession httpSession = getHttpSession(httpServletRequest);
+        if(authSession != null) {
+            httpSession.setAttribute(WebAppConstants.HTTP_SESSION_AUTHENTICATION_SESSION_KEY, authSession);
+        } else {
+            httpSession.removeAttribute(WebAppConstants.HTTP_SESSION_AUTHENTICATION_SESSION_KEY);
+        }
     }
 
+
     // //////////////////////////////////////////////////////////
     // Dependencies (from context)
     // //////////////////////////////////////////////////////////

http://git-wip-us.apache.org/repos/asf/isis/blob/edc4fa76/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/Rel.java
----------------------------------------------------------------------
diff --git a/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/Rel.java b/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/Rel.java
index 5381e1b..70d7699 100644
--- a/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/Rel.java
+++ b/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/Rel.java
@@ -58,7 +58,8 @@ public enum Rel {
     
 
     // implementation specific
-    CONTRIBUTED_BY(RelDefinition.IMPL, "contributed-by");
+    CONTRIBUTED_BY(RelDefinition.IMPL, "contributed-by"),
+    LOGOUT(RelDefinition.IMPL, "logout");
 
     private final RelDefinition relDef;
     private final String relSuffix;

http://git-wip-us.apache.org/repos/asf/isis/blob/edc4fa76/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/user/UserResource.java
----------------------------------------------------------------------
diff --git a/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/user/UserResource.java b/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/user/UserResource.java
index ff53c07..afcc179 100644
--- a/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/user/UserResource.java
+++ b/core/viewer-restfulobjects-applib/src/main/java/org/apache/isis/viewer/restfulobjects/applib/user/UserResource.java
@@ -18,13 +18,19 @@
  */
 package org.apache.isis.viewer.restfulobjects.applib.user;
 
-import javax.ws.rs.*;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 
-import org.apache.isis.viewer.restfulobjects.applib.RestfulMediaType;
 import org.jboss.resteasy.annotations.ClientResponseType;
 
+import org.apache.isis.viewer.restfulobjects.applib.RestfulMediaType;
+
 @Path("/user")
 public interface UserResource {
 
@@ -42,4 +48,13 @@ public interface UserResource {
     @POST
     public Response postUserNotAllowed();
 
+    /**
+     * Not part of the Restful Objects spec.
+     */
+    @GET
+    @Path("/logout")
+    @Produces({ MediaType.APPLICATION_JSON, RestfulMediaType.APPLICATION_JSON_HOME_PAGE })
+    @ClientResponseType(entityType = String.class)
+    public Response logout();
+
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/edc4fa76/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/RendererContext.java
----------------------------------------------------------------------
diff --git a/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/RendererContext.java b/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/RendererContext.java
index 03ab027..a1b0821 100644
--- a/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/RendererContext.java
+++ b/core/viewer-restfulobjects-rendering/src/main/java/org/apache/isis/viewer/restfulobjects/rendering/RendererContext.java
@@ -35,7 +35,7 @@ public interface RendererContext {
     public String urlFor(final String url);
 
     public AuthenticationSession getAuthenticationSession();
-    
+
     public IsisConfiguration getConfiguration();
     
     public PersistenceSession getPersistenceSession();

http://git-wip-us.apache.org/repos/asf/isis/blob/edc4fa76/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/authentication/AuthenticationSessionStrategyBasicAuth.java
----------------------------------------------------------------------
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/authentication/AuthenticationSessionStrategyBasicAuth.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/authentication/AuthenticationSessionStrategyBasicAuth.java
index 54bd2b6..f8f69e5 100644
--- a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/authentication/AuthenticationSessionStrategyBasicAuth.java
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/authentication/AuthenticationSessionStrategyBasicAuth.java
@@ -21,9 +21,8 @@ package org.apache.isis.viewer.restfulobjects.server.authentication;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
 import org.apache.commons.codec.binary.Base64;
@@ -40,22 +39,20 @@ import org.apache.isis.core.webapp.auth.AuthenticationSessionStrategyAbstract;
  */
 public class AuthenticationSessionStrategyBasicAuth extends AuthenticationSessionStrategyAbstract {
 
+    public static final String HEADER_AUTHORIZATION = "Authorization";
+    public static final String BASIC_AUTH_PREFIX = "Basic ";
+
     private static Pattern USER_AND_PASSWORD_REGEX = Pattern.compile("^(.+):(.+)$");
 
     @Override
-    public AuthenticationSession lookupValid(final ServletRequest servletRequest, final ServletResponse servletResponse) {
-
-        final HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
-        final String authStr = httpServletRequest.getHeader("Authorization");
+    public AuthenticationSession lookupValid(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse) {
 
-        // value should be in the form:
-        // Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
-        if (authStr == null || !authStr.startsWith("Basic ")) {
+        final String digest = getBasicAuthDigest(httpServletRequest);
+        if (digest == null) {
             return null;
         }
-        final String digest = authStr.substring(6);
 
-        final String userAndPassword = new String(new Base64().decode(digest.getBytes()));
+        final String userAndPassword = unencoded(digest);
         final Matcher matcher = USER_AND_PASSWORD_REGEX.matcher(userAndPassword);
         if (!matcher.matches()) {
             return null;
@@ -64,10 +61,28 @@ public class AuthenticationSessionStrategyBasicAuth extends AuthenticationSessio
         final String user = matcher.group(1);
         final String password = matcher.group(2);
 
-        final AuthenticationSession authSession = getAuthenticationManager().authenticate(new AuthenticationRequestPassword(user, password));
+        final AuthenticationRequestPassword request = new AuthenticationRequestPassword(user, password);
+        final AuthenticationSession authSession =
+                getAuthenticationManager().authenticate(request);
         return authSession;
     }
 
+    // value should be in the form:
+    // Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
+    String getBasicAuthDigest(final HttpServletRequest httpServletRequest) {
+        final String authStr = httpServletRequest.getHeader(HEADER_AUTHORIZATION);
+        return authStr != null &&
+                authStr.startsWith(BASIC_AUTH_PREFIX)
+                ? authStr.substring(BASIC_AUTH_PREFIX.length())
+                : null;
+    }
+
+
+    protected String unencoded(final String encodedDigest) {
+        return new String(new Base64().decode(encodedDigest.getBytes()));
+    }
+
+
     // //////////////////////////////////////////////////////////
     // Dependencies (from context)
     // //////////////////////////////////////////////////////////

http://git-wip-us.apache.org/repos/asf/isis/blob/edc4fa76/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/authentication/AuthenticationSessionStrategyHeader.java
----------------------------------------------------------------------
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/authentication/AuthenticationSessionStrategyHeader.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/authentication/AuthenticationSessionStrategyHeader.java
index 84bfb73..cece9a6 100644
--- a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/authentication/AuthenticationSessionStrategyHeader.java
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/authentication/AuthenticationSessionStrategyHeader.java
@@ -21,9 +21,8 @@ package org.apache.isis.viewer.restfulobjects.server.authentication;
 import java.util.Collections;
 import java.util.List;
 
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpSession;
 
 import com.google.common.base.Splitter;
@@ -43,11 +42,12 @@ import org.apache.isis.core.webapp.auth.AuthenticationSessionStrategyAbstract;
  */
 public class AuthenticationSessionStrategyHeader extends AuthenticationSessionStrategyAbstract {
 
+    public static final String HEADER_ISIS_USER = "isis.user";
+
     @Override
-    public AuthenticationSession lookupValid(final ServletRequest servletRequest, final ServletResponse servletResponse) {
+    public AuthenticationSession lookupValid(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse) {
 
-        final HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
-        final String user = httpServletRequest.getHeader("isis.user");
+        final String user = httpServletRequest.getHeader(HEADER_ISIS_USER);
         final List<String> roles = rolesFrom(httpServletRequest);
 
         if (Strings.isNullOrEmpty(user)) {

http://git-wip-us.apache.org/repos/asf/isis/blob/edc4fa76/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/authentication/AuthenticationSessionStrategyTrusted.java
----------------------------------------------------------------------
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/authentication/AuthenticationSessionStrategyTrusted.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/authentication/AuthenticationSessionStrategyTrusted.java
index 666ccc3..16fbfc7 100644
--- a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/authentication/AuthenticationSessionStrategyTrusted.java
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/authentication/AuthenticationSessionStrategyTrusted.java
@@ -18,8 +18,8 @@
  */
 package org.apache.isis.viewer.restfulobjects.server.authentication;
 
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 
 import org.apache.isis.core.commons.authentication.AuthenticationSession;
 import org.apache.isis.core.runtime.authentication.exploration.AuthenticationRequestExploration;
@@ -29,8 +29,8 @@ import org.apache.isis.core.webapp.auth.AuthenticationSessionStrategyDefault;
 public class AuthenticationSessionStrategyTrusted extends AuthenticationSessionStrategyDefault {
 
     @Override
-    public AuthenticationSession lookupValid(final ServletRequest servletRequest, final ServletResponse servletResponse) {
-        final AuthenticationSession session = super.lookupValid(servletRequest, servletResponse);
+    public AuthenticationSession lookupValid(final HttpServletRequest httpServletRequest, final HttpServletResponse httpServletResponse) {
+        final AuthenticationSession session = super.lookupValid(httpServletRequest, httpServletResponse);
         if (session != null) {
             return session;
         }

http://git-wip-us.apache.org/repos/asf/isis/blob/edc4fa76/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/ResourceAbstract.java
----------------------------------------------------------------------
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/ResourceAbstract.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/ResourceAbstract.java
index c014111..a570487 100644
--- a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/ResourceAbstract.java
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/ResourceAbstract.java
@@ -20,9 +20,14 @@ package org.apache.isis.viewer.restfulobjects.server.resources;
 
 import java.io.InputStream;
 import java.util.List;
+
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import javax.ws.rs.core.*;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Request;
+import javax.ws.rs.core.SecurityContext;
+import javax.ws.rs.core.UriInfo;
 import javax.ws.rs.ext.Providers;
 
 import org.apache.isis.applib.annotation.Where;
@@ -35,6 +40,7 @@ import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
 import org.apache.isis.core.metamodel.adapter.oid.OidMarshaller;
 import org.apache.isis.core.metamodel.services.ServiceUtil;
 import org.apache.isis.core.metamodel.spec.SpecificationLoaderSpi;
+import org.apache.isis.core.runtime.authentication.AuthenticationManager;
 import org.apache.isis.core.runtime.system.context.IsisContext;
 import org.apache.isis.core.runtime.system.persistence.PersistenceSession;
 import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
@@ -155,6 +161,10 @@ public abstract class ResourceAbstract {
         return IsisContext.getAuthenticationSession();
     }
 
+    protected AuthenticationManager getAuthenticationManager() {
+        return IsisContext.getAuthenticationManager();
+    }
+
     protected SpecificationLoaderSpi getSpecificationLoader() {
         return IsisContext.getSpecificationLoader();
     }

http://git-wip-us.apache.org/repos/asf/isis/blob/edc4fa76/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/UserReprRenderer.java
----------------------------------------------------------------------
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/UserReprRenderer.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/UserReprRenderer.java
index 6cd15bd..e37d4be 100644
--- a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/UserReprRenderer.java
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/UserReprRenderer.java
@@ -47,6 +47,7 @@ public class UserReprRenderer extends ReprRendererAbstract<UserReprRenderer, Aut
         if (includesSelf) {
             addLinkToSelf();
             addLinkToUp();
+            addLinkToLogout();
         }
         getExtensions();
         return representation;
@@ -76,5 +77,11 @@ public class UserReprRenderer extends ReprRendererAbstract<UserReprRenderer, Aut
         getLinks().arrayAdd(link);
     }
 
+    private void addLinkToLogout() {
+        final JsonRepresentation link = LinkBuilder.newBuilder(rendererContext, Rel.LOGOUT.getName(), RepresentationType.HOME_PAGE, "user/logout").build();
+
+        getLinks().arrayAdd(link);
+    }
+
 
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/isis/blob/edc4fa76/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/UserResourceServerside.java
----------------------------------------------------------------------
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/UserResourceServerside.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/UserResourceServerside.java
index 9d1b0c6..0910aeb 100644
--- a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/UserResourceServerside.java
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/UserResourceServerside.java
@@ -18,11 +18,15 @@
  */
 package org.apache.isis.viewer.restfulobjects.server.resources;
 
+import java.net.URI;
+import java.net.URISyntaxException;
+
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 
 import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.core.webapp.IsisSessionFilter;
 import org.apache.isis.viewer.restfulobjects.applib.JsonRepresentation;
 import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
 import org.apache.isis.viewer.restfulobjects.applib.RestfulMediaType;
@@ -62,4 +66,28 @@ public class UserResourceServerside extends ResourceAbstract implements UserReso
         throw RestfulObjectsApplicationException.createWithMessage(RestfulResponse.HttpStatusCode.METHOD_NOT_ALLOWED, "Posting to the user resource is not allowed.");
     }
 
+    /**
+     * Not part of the Restful Objects spec.
+     */
+    @Override
+    @Produces({ MediaType.APPLICATION_JSON, RestfulMediaType.APPLICATION_JSON_HOME_PAGE })
+    public Response logout() {
+        init(RepresentationType.HOME_PAGE, Where.NOWHERE);
+
+        final HomePageReprRenderer renderer = new HomePageReprRenderer(getResourceContext(), null, JsonRepresentation.newMap());
+        renderer.includesSelf();
+
+        // we do the logout (removes this session from those valid)
+        getAuthenticationManager().closeSession(getResourceContext().getAuthenticationSession());
+
+        // we also redirect to home page with special query string; this allows the session filter
+        // to clear out any cookies/headers (eg if BASIC auth in use).
+        try {
+            final URI location = new URI("?" + IsisSessionFilter.QUERY_STRING_FORCE_LOGOUT);
+            return Response.temporaryRedirect(location).build();
+        } catch (URISyntaxException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/edc4fa76/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/webapp/IsisTransactionFilterForRestfulObjects.java
----------------------------------------------------------------------
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/webapp/IsisTransactionFilterForRestfulObjects.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/webapp/IsisTransactionFilterForRestfulObjects.java
index acc107b..af943ab 100644
--- a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/webapp/IsisTransactionFilterForRestfulObjects.java
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/webapp/IsisTransactionFilterForRestfulObjects.java
@@ -40,10 +40,18 @@ public class IsisTransactionFilterForRestfulObjects implements Filter {
         try {
             chain.doFilter(request, response);
         } finally {
-            getTransactionManager().endTransaction();
+            final boolean inTransaction = inTransaction();
+            if(inTransaction) {
+                // user/logout will have invalidated the current transaction and also persistence session.
+                getTransactionManager().endTransaction();
+            }
         }
     }
 
+    protected boolean inTransaction() {
+        return IsisContext.inTransaction();
+    }
+
     protected IsisTransactionManager getTransactionManager() {
         return IsisContext.getTransactionManager();
     }