org.kmnet.com.fw.web.token.transaction.TransactionTokenInterceptor.java Source code

Java tutorial

Introduction

Here is the source code for org.kmnet.com.fw.web.token.transaction.TransactionTokenInterceptor.java

Source

/*
 * Copyright (C) 2016 KnowledgeNet.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
/*
 * Modified by KnowledgeNet Inc.
 * <li>Ver.1.0 2016/02/14 ??(Shape-Up)</li>
 */
package org.kmnet.com.fw.web.token.transaction;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.kmnet.com.fw.web.token.TokenStringGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.util.WebUtils;

/**
 * {@code HandlerInterceptor} implementation class that introduces TransactionTokenCheck functionality for each incoming HTTP
 * Request.
 */
public class TransactionTokenInterceptor implements HandlerInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(TransactionTokenInterceptor.class);

    /**
     * attribute name of {@link TransactionTokenContext} in the request scope
     */
    public static final String TOKEN_CONTEXT_REQUEST_ATTRIBUTE_NAME = TransactionTokenInterceptor.class.getName()
            + ".TOKEN_CONTEXT";

    /**
     * attribute name of next {@link TransactionToken} in the request scope
     */
    public static final String NEXT_TOKEN_REQUEST_ATTRIBUTE_NAME = TransactionTokenInterceptor.class.getName()
            + ".NEXT_TOKEN";

    /**
     * request parameter of token value to check
     */
    public static final String TOKEN_REQUEST_PARAMETER = "_TRANSACTION_TOKEN";

    /**
     * invalid token constant
     */
    private static final TransactionToken INVALID_TOKEN = new TransactionToken(null, null, null) {
        /**
         * serial version UID.
         */
        private static final long serialVersionUID = 674844591801033738L;

        @Override
        public boolean valid() {
            return false;
        }
    };

    /**
     * Store for TransactionTokenInfo objects
     */
    private final TransactionTokenInfoStore tokenInfoStore;

    /**
     * Store for TransactionToken objects
     */
    private final TransactionTokenStore tokenStore;

    /**
     * Token string generator
     */
    private final TokenStringGenerator generator;

    /**
     * Constructor that takes sizePerTokenName.
     * <p>
     * sizePerTokenName is the number of tokenKeys that are allowed to be generated per tokenName.<br>
     * {@link TokenStringGenerator} is used to generate token string.<br>
     * {@link TransactionTokenInfoStore} is used to store {@link TransactionTokenInfo}.<br>
     * {@link HttpSessionTransactionTokenStore} is used to store transaction tokens. Default size per tokenName(namespace) is
     * used.
     * </p>
     * @see HttpSessionTransactionTokenStore#HttpSessionTransactionTokenStore()
     */
    public TransactionTokenInterceptor() {
        this(new TokenStringGenerator(), new TransactionTokenInfoStore(), new HttpSessionTransactionTokenStore());
    }

    /**
     * Constructor that takes sizePerTokenName.
     * <p>
     * sizePerTokenName is the number of tokenKeys that are allowed to be generated per tokenName.<br>
     * {@link TokenStringGenerator} is used to generate token string.<br>
     * {@link TransactionTokenInfoStore} is used to store {@link TransactionTokenInfo}.<br>
     * {@link HttpSessionTransactionTokenStore} is used to store transaction tokens. The size per tokenName(namespace) is given.
     * </p>
     * @param sizePerTokenName size per tokenName(must be greater than 0)
     * @see HttpSessionTransactionTokenStore#HttpSessionTransactionTokenStore(int)
     */
    public TransactionTokenInterceptor(int sizePerTokenName) {
        this(new TokenStringGenerator(), new TransactionTokenInfoStore(),
                new HttpSessionTransactionTokenStore(sizePerTokenName));
    }

    /**
     * Constructor that takes tokenStringGenerator, transactionTokenInfoStore and transactionTokenStore as parameters
     * @param generator token string generator
     * @param tokenInfoStore store for {@link TransactionTokenInfo}
     * @param tokenStore store for {@link TransactionToken}
     */
    public TransactionTokenInterceptor(TokenStringGenerator generator, TransactionTokenInfoStore tokenInfoStore,
            TransactionTokenStore tokenStore) {
        this.generator = generator;
        this.tokenInfoStore = tokenInfoStore;
        this.tokenStore = tokenStore;
    }

    /**
     * Validates the token received from request. <br>
     * <p>
     * If token check fails, returns false. <br>
     * If token check passes, sets context information of the token in the "TransactionTokenInterceptor.TOKEN_CONTEXT" request
     * attribute and returns true.
     * <p>
     * This method expects the handler argument to be an instance of <code>HandlerMethod</code> class. If the handler is not an
     * instance of <code>HandlerMethod</code> class, the method returns true without executing the validation. <br>
     * @throws InvalidTransactionTokenException in case of Transaction Token Validation error.<br>
     * @see org.springframework.web.servlet.HandlerInterceptor#preHandle(javax.servlet.http.HttpServletRequest,
     *      javax.servlet.http.HttpServletResponse, java.lang.Object)
     */
    @Override
    public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response,
            final Object handler) {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        logger.trace("preHandle");

        HandlerMethod handlerMethod = (HandlerMethod) handler;
        TransactionTokenInfo tokenInfo = tokenInfoStore.getTransactionTokenInfo(handlerMethod);

        TransactionToken receivedToken = INVALID_TOKEN;
        if (tokenInfo.needValidate()) {
            receivedToken = createReceivedToken(request);
            if (!validateToken(receivedToken, tokenStore, tokenInfo)) {
                processTransactionTokenError(receivedToken);
            }
        }
        if (tokenInfo.getTransactionTokenType() == TransactionTokenType.BEGIN) {
            // This logic is added later to remove existing token sent in the request in case of Transaction BEGIN
            // When transactions BEGIN, the transaction token sent from the request are usually those which are generated
            // due to input errors. (Since method with BEGIN may be called in spite of input errors, and token will be generated
            // in the intercepter postHandle method execution.

            // This logic is added here to minimize the impact to any other existing logic

            String tokenStr = request.getParameter(TOKEN_REQUEST_PARAMETER);
            if (null != tokenStr) {
                removeToken(new TransactionToken(tokenStr));
            }
        }

        TransactionTokenContextImpl tokenContext = new TransactionTokenContextImpl(tokenInfo, receivedToken);

        request.setAttribute(TOKEN_CONTEXT_REQUEST_ATTRIBUTE_NAME, tokenContext);

        return true;
    }

    protected void processTransactionTokenError(TransactionToken receivedToken) {
        removeToken(receivedToken);
        throw new InvalidTransactionTokenException();
    }

    /**
     * Retrieves the value of transactionToken fetched from a request parameter
     * @param request
     * @return currentToken transactionToken received from the request
     */
    TransactionToken createReceivedToken(final HttpServletRequest request) {
        String tokenStr = request.getParameter(TOKEN_REQUEST_PARAMETER);
        TransactionToken currentToken = new TransactionToken(tokenStr);
        return currentToken;
    }

    /**
     * Validates the instance of receivedToken. <br>
     * <p>
     * Returns false if the instance of receivedToken is not valid or token namespace of the target method and passed token does
     * not match. <br>
     * Returns true if <br>
     * a) it is saved in tokenStore AND <br>
     * b) Value of receivedToken is same as the value of token stored in tokenStore <br>
     * Returns false otherwise <br>
     * <p>
     * Take note that once a token is fetched from tokenStore, its value is cleared in tokenStore. Hence this method can return
     * true only once. All further invocations to this method for the same receivedToken will return false.
     * @param receivedToken
     * @param tokenStore
     * @param tokenInfo
     * @return if valid token, return true
     */
    boolean validateToken(final TransactionToken receivedToken, final TransactionTokenStore tokenStore,
            final TransactionTokenInfo tokenInfo) {

        if (receivedToken.valid() && receivedToken.getTokenName().equals(tokenInfo.getTokenName())) {

            String storedToken = tokenStore.getAndClear(receivedToken);
            if (storedToken != null && storedToken.equals(receivedToken.getTokenValue())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Based on context information from the request attribute named <code>TransactionTokenInterceptor.TOKEN_CONTEXT</code>,
     * creates or updates the token stored with the request attribute <code>TransactionTokenInterceptor.NEXT_TOKEN</code> and
     * also in the <code>TransactionTokenStore</code> or removes the token from <code>TransactionTokenStore</code>
     * <p>
     * modelAndView is not used in the implementation
     * @param request current HTTP request
     * @param response current HTTP response
     * @param handler chosen handler to execute, for type and/or instance examination
     * @param modelAndView the <code>ModelAndView</code> that the handler returned (can also be <code>null</code>)
     * @throws Exception in case of errors
     * @see org.springframework.web.servlet.HandlerInterceptor#postHandle(javax.servlet.http.HttpServletRequest,
     *      javax.servlet.http.HttpServletResponse, java.lang.Object, org.springframework.web.servlet.ModelAndView)
     */
    @Override
    public void postHandle(final HttpServletRequest request, final HttpServletResponse response,
            final Object handler, final ModelAndView modelAndView) {

        logger.trace("postHandle");

        if (!(handler instanceof HandlerMethod)) {
            return;
        }

        TransactionTokenContextImpl tokenContext = (TransactionTokenContextImpl) request
                .getAttribute(TOKEN_CONTEXT_REQUEST_ATTRIBUTE_NAME);

        switch (tokenContext.getReserveCommand()) {
        case CREATE_TOKEN:
            createToken(request, request.getSession(true), tokenContext.getTokenInfo(), generator, tokenStore);
            break;
        case UPDATE_TOKEN:
            updateToken(request, request.getSession(true), tokenContext.getReceivedToken(),
                    tokenContext.getTokenInfo(), generator, tokenStore);
            break;
        case REMOVE_TOKEN:
            removeToken(tokenContext.getReceivedToken());
            break;
        default:
            // noop
            break;
        }

    }

    /**
     * If exception occurred during request processing, the token is removed from request as well as
     * <code>TransactionTokenStore<code> 
     * <p>
     * Token Context is fetched from the request attribute named <code>TransactionTokenInterceptor.TOKEN_CONTEXT</code>
     * Arguments <code>response</code> and <code>handler</code> are not used in this implementation
     * @see org.springframework.web.servlet.HandlerInterceptor#afterCompletion(javax.servlet.http.HttpServletRequest,
     *      javax.servlet.http.HttpServletResponse, java.lang.Object, java.lang.Exception)
     */
    @Override
    public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response,
            final Object handler, final Exception ex) {
        logger.trace("afterCompletion");

        if (ex != null) {
            TransactionTokenContextImpl tokenContext = (TransactionTokenContextImpl) request
                    .getAttribute(TOKEN_CONTEXT_REQUEST_ATTRIBUTE_NAME);
            TransactionToken token = tokenContext.getReceivedToken();
            removeToken(token);
        }

    }

    /**
     * Updates the value of existing token in <code>TransactionTokenStore</code> as well as in the request
     * <p>
     * Updated <code>TransactionToken</code> instance is set to request attribute
     * <code>TransactionTokenInterceptor.NEXT_TOKEN</code>.
     * <p>
     * @param request
     * @param session
     * @param receivedToken
     * @param tokenInfo
     * @param generator
     * @param tokenStore
     */
    void updateToken(HttpServletRequest request, HttpSession session, TransactionToken receivedToken,
            TransactionTokenInfo tokenInfo, TokenStringGenerator generator, TransactionTokenStore tokenStore) {
        TransactionToken nextToken = new TransactionToken(tokenInfo.getTokenName(), receivedToken.getTokenKey(),
                generator.generate(session.getId()));
        tokenStore.store(nextToken);
        request.setAttribute(NEXT_TOKEN_REQUEST_ATTRIBUTE_NAME, nextToken);
    }

    /**
     * Creates a new <code>TransactionToken</code> <br>
     * <p>
     * Generated <code>TransactionToken</code> instance is stored in <code>TransactionTokenStore</code> and also set to request
     * attribute <code>TransactionTokenInterceptor.NEXT_TOKEN</code>.
     * <p>
     * @param request
     * @param session
     * @param tokenInfo TransactionTokenInfo
     * @param generator TokenStringGenerator
     * @param tokenStore TransactionTokenStore
     */
    void createToken(HttpServletRequest request, HttpSession session, TransactionTokenInfo tokenInfo,
            TokenStringGenerator generator, TransactionTokenStore tokenStore) {
        TransactionToken nextToken;
        synchronized (WebUtils.getSessionMutex(session)) {
            String tokenKey = tokenStore.createAndReserveTokenKey(tokenInfo.getTokenName());
            nextToken = new TransactionToken(tokenInfo.getTokenName(), tokenKey,
                    generator.generate(session.getId()));
            tokenStore.store(nextToken);
        }
        request.setAttribute(NEXT_TOKEN_REQUEST_ATTRIBUTE_NAME, nextToken);
    }

    /**
     * Removes the receivedToken received as parameter to this method, from the tokenStore
     * @param receivedToken
     */
    void removeToken(TransactionToken receivedToken) {
        if (receivedToken.valid()) {
            tokenStore.remove(receivedToken);
        }
    }
}