001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.mina.example.tapedeck;
021
022import static org.apache.mina.statemachine.event.IoFilterEvents.*;
023
024import org.apache.mina.core.filterchain.IoFilter.NextFilter;
025import org.apache.mina.core.future.IoFutureListener;
026import org.apache.mina.core.session.IoSession;
027import org.apache.mina.core.write.WriteRequest;
028import org.apache.mina.example.tapedeck.TapeDeckServer.TapeDeckContext;
029import org.apache.mina.statemachine.StateControl;
030import org.apache.mina.statemachine.annotation.IoFilterTransition;
031import org.apache.mina.statemachine.annotation.State;
032import org.apache.mina.statemachine.context.AbstractStateContext;
033
034/**
035 * TODO Add documentation
036 * 
037 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
038 */
039public class AuthenticationHandler {
040    @State public static final String ROOT = "Root";
041    @State(ROOT) public static final String START = "Start";
042    @State(ROOT) public static final String WAIT_USER = "WaitUser";
043    @State(ROOT) public static final String WAIT_PASSWORD = "WaitPassword";
044    @State(ROOT) public static final String DONE = "Done";
045    @State(ROOT) public static final String FAILED = "Failed";
046
047    static class AuthenticationContext extends AbstractStateContext {
048        private String user;
049        private String password;
050        private int tries = 0;
051    }
052
053    @IoFilterTransition(on = SESSION_OPENED, in = START, next = WAIT_USER)
054    public void sendAuthRequest(NextFilter nextFilter, IoSession session) {
055        session.write("+ Greetings from your tape deck!. Please authenticate yourself.");
056    }
057    
058    @IoFilterTransition(on = MESSAGE_RECEIVED, in = WAIT_USER, next = WAIT_PASSWORD)
059    public void user(AuthenticationContext context, NextFilter nextFilter, IoSession session, UserCommand cmd) {
060        context.user = cmd.getUsername();
061        session.write("+ Give me your password (hint: try your username backwards)");
062    }
063    
064    @IoFilterTransition(on = MESSAGE_RECEIVED, in = WAIT_PASSWORD, next = DONE)
065    public void password(AuthenticationContext context, NextFilter nextFilter, IoSession session, PasswordCommand cmd) {
066        context.password = cmd.getPassword();
067        if (context.password.equals(reverse(context.user))) {
068            session.write("+ Authentication succeeded! Your tape deck has been unlocked.");
069        } else {
070            context.tries++;
071            if (context.tries < 3) {
072                session.write("- Authentication failed! Please try again.");
073                StateControl.breakAndGotoNext(WAIT_USER);
074            } else {
075                session.write("- Authentication failed too many times! Bye bye.").addListener(IoFutureListener.CLOSE);
076                StateControl.breakAndGotoNext(FAILED);
077            }
078        }
079    }
080    
081    
082    @IoFilterTransition(on = MESSAGE_RECEIVED, in = ROOT)
083    public void quit(TapeDeckContext context, IoSession session, QuitCommand cmd) {
084        session.write("+ Bye! Please come back!").addListener(IoFutureListener.CLOSE);
085    }
086    
087    private String reverse(String s) {
088        char[] expectedPassword = new char[s.length()];
089        for (int i = 0; i < expectedPassword.length; i++) {
090            expectedPassword[i] = s.charAt(s.length() - i - 1);
091        }
092        return new String(expectedPassword);
093    }
094    
095    @IoFilterTransition(on = MESSAGE_RECEIVED, in = WAIT_USER, weight = 10)
096    public void errorWaitingForUser(IoSession session, Command cmd) {
097        if (cmd instanceof QuitCommand) {
098            StateControl.breakAndContinue();
099        }
100        session.write("Unexpected command '" + cmd.getName() + "'. Expected 'user <username>'.");
101    }
102    
103    @IoFilterTransition(on = MESSAGE_RECEIVED, in = WAIT_PASSWORD, weight = 10)
104    public void errorWaitingForPassword(IoSession session, Command cmd) {
105        if (cmd instanceof QuitCommand) {
106            StateControl.breakAndContinue();
107        }
108        session.write("Unexpected command '" + cmd.getName() + "'. Expected 'password <password>'.");
109    }
110    
111    @IoFilterTransition(on = EXCEPTION_CAUGHT, in = ROOT)
112    public void commandSyntaxError(IoSession session, CommandSyntaxException e) {
113        session.write("- " + e.getMessage());
114    }
115    
116    @IoFilterTransition(on = EXCEPTION_CAUGHT, in = ROOT, weight = 10)
117    public void exceptionCaught(IoSession session, Exception e) {
118        e.printStackTrace();
119        session.closeNow();
120    }
121    
122    @IoFilterTransition(on = SESSION_CLOSED, in = DONE)
123    public void sessionClosed(NextFilter nextFilter, IoSession session) {
124        nextFilter.sessionClosed(session);
125    }
126    @IoFilterTransition(on = EXCEPTION_CAUGHT, in = DONE)
127    public void exceptionCaught(NextFilter nextFilter, IoSession session, Throwable cause) {
128        nextFilter.exceptionCaught(session, cause);
129    }
130    @IoFilterTransition(on = MESSAGE_RECEIVED, in = DONE)
131    public void messageReceived(NextFilter nextFilter, IoSession session, Object message) {
132        nextFilter.messageReceived(session, message);
133    }
134    @IoFilterTransition(on = MESSAGE_SENT, in = DONE)
135    public void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) {
136        nextFilter.messageSent(session, writeRequest);
137    }
138    
139    @IoFilterTransition(on = CLOSE, in = ROOT)
140    public void filterClose(NextFilter nextFilter, IoSession session) {
141        nextFilter.filterClose(session);
142    }
143    @IoFilterTransition(on = WRITE, in = ROOT)
144    public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) {
145        nextFilter.filterWrite(session, writeRequest);
146    }
147}