All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.nustaq.kontraktor.weblication.BasicWebAppActor Maven / Gradle / Ivy

The newest version!
package org.nustaq.kontraktor.weblication;

import io.undertow.server.HttpServerExchange;
import io.undertow.util.Headers;
import org.nustaq.kontraktor.*;
import org.nustaq.kontraktor.annotations.CallerSideMethod;
import org.nustaq.kontraktor.annotations.Local;
import org.nustaq.kontraktor.annotations.Remoted;
import org.nustaq.kontraktor.impl.SimpleScheduler;
import org.nustaq.kontraktor.remoting.base.ConnectionRegistry;
import org.nustaq.kontraktor.remoting.base.SessionResurrector;
import org.nustaq.kontraktor.util.Log;
import org.nustaq.kontraktor.weblication.model.DefaultSessionStorage;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by ruedi on 20.06.17.
 */
public abstract class BasicWebAppActor extends Actor implements SessionResurrector {

    public static final String SESSIONID_SPECIAL_STRING = "SESSION_ID";
    public static String WEBAPP_DIR = "./src/main/web/client";
    public static String BASH_EXEC = "/usr/bin/bash";
    public static String BABEL_SERVER_JS_PATH = "./node_modules/babelserver/babelserver.js";

    protected Scheduler[] sessionThreads;
    protected ISessionStorage sessionStorage;
    protected Map sessions;

    @Local
    public void init(C config) {
        int numSessionThreads = config.getNumSessionThreads();
        sessionThreads = new Scheduler[numSessionThreads];
        for (int i = 0; i < numSessionThreads; i++ ) {
            sessionThreads[i] = new SimpleScheduler(10_000, true);
        }
        sessionStorage = createSessionStorage(config);
        sessions = new HashMap<>();
    }

    protected ISessionStorage createSessionStorage(C config) {
        DefaultSessionStorage defaultSessionStorage = AsActor(DefaultSessionStorage.class);
        defaultSessionStorage.init(new DefaultSessionStorage.Config()).await(60_000);
        return defaultSessionStorage;
    }

    /**
     * returns an array of [session actorproxy, userdata]
     * @param user
     * @param pw
     * @param jwt
     * @return array [session actor proxy, authenticationresult]
     */
    @Remoted
    public IPromise login(String user, String pw, String jwt) {
        Promise res = new Promise();
        ConnectionRegistry remoteConnection = connection.get();
        getCredentials(user,pw,jwt).then( (authres, err) -> {
            if ( err != null ) {
                res.reject(err);
            } else {
                String sessionId = remoteConnection != null ? remoteConnection.getSocketRef().getConnectionIdentifier() : null;
                getSession(user,sessionId, authres ).then( (sess,serr) -> {
                    if ( sess != null ) {
                        if ( sessionId != null && sessionStorage != null ) {
                            sessionStorage.putUserAtSessionId(sessionId,user);
                        }
                        res.resolve(new Object[]{sess,authres});
                    } else
                        res.complete(sess,serr);
                });
            }
        });
        return res;
    }

    /**
     * does a lookup for a user record using 'user' as key. Compares pw with 'pwd' of user record if found
     * if user denotes a vaild old session id and pwd == 'SESSION_ID', a session is created from a previous authentication
     *
     * @param pw
     * @param jwt
     * @return
     */
    protected IPromise getCredentials(String user, String pw, String jwt) {
        Promise p = new Promise();
        if ( SESSIONID_SPECIAL_STRING.equals(pw) ) {
            sessionStorage.getUserFromSessionId(user).then( (r,e) -> {
                if ( r != null ) {
                    sessionStorage.getUser(r).then( (rec,err) -> {
                        if ( rec != null )
                            getCredentials(rec.getKey(),rec.getString("pwd"), null ).then(p);
                        else
                            p.reject("unknown user");
                    });
                } else {
                    p.reject("authentication failed");
                }
            });
        } else {
            sessionStorage.getUser(user).then((rec, err) -> {
                if (rec == null) {
                    p.reject("wrong user or password");
                } else {
                    if (pw.equals(rec.getString("pwd"))) {
                        if (!rec.getBool("verified")) {
                            // e.g. email verification etc.
                            p.reject("account not verified");
                        } else
                            p.resolve(new BasicAuthenticationResult().userName(rec.getKey()));
                    }
                }
            });
        }
        return p;
    }

    protected IPromise getSessionForReanimation(String user, String sessionId) {
        return getSession(user,sessionId,new BasicAuthenticationResult().userName(user));
    }

    protected IPromise getSession(String user, String sessionId, BasicAuthenticationResult authenticationResult) {
        // session sharing has major implications on resurrection and callback / subscription handling
//        BasicWebSessionActor sess = sessions.get(user);
//        if ( sess != null && ! sess.isStopped() ) {
//            return sess;
//        }
        return createSession(user,sessionId,authenticationResult);
    }

    protected IPromise createSession(String user, String sessionId, BasicAuthenticationResult authenticationResult) {
        Promise p = new Promise();
        BasicWebSessionActor sess = AsActor((Class) getSessionClazz(),sessionThreads[(int) (Math.random()*sessionThreads.length)]);
        sess.init(self(),authenticationResult,sessionId).then( (res,err) -> {
            if ( err == null ) {
                sessions.put(sessionId,sess);
                authenticationResult.initialData(res);
                p.resolve(sess);
            } else
                p.reject(err);
        });
        return p;
    }

    protected abstract Class getSessionClazz(); /* {
        return BasicWebSessionActor.class;
    }*/

    /**
     * An existing spa client made a request after being inactive for a long time.
     *
     * mission: reconstruct a session actor and load its state (e.g. from persistence) so it can continue or
     * return null and handle "session expired" correctly at client side. Note there is a default implementation
     *
     * session resurrection is called using await => hurry up
     *
     * return new Promise(null) in order to bypass session reanimation default implementation
     *
     * @param sessionId
     * @param remoteRefId
     * @return
     *
     * @param sessionId
     * @param remoteRefId
     * @return resurrected BasicWebSessionActor
     */
    @Override @Local
    public IPromise reanimate(String sessionId, long remoteRefId) {
        if ( sessionStorage == null )
            return resolve(null);
        Promise res = new Promise();
        sessionStorage.getUserFromSessionId(sessionId).then( (user, err) -> {
            if ( user == null )
                res.resolve(null);
            else {
                getSessionForReanimation(user, sessionId).then( res );
            }
        });
        return res;
    }

    @Local
    public void notifySessionEnd(BasicWebSessionActor session) {
        sessions.remove(session.getSessionId());
        Log.Info(this, "session timed out "+session.getSessionId()+" "+session.getUserKey()+" sessions:"+sessions.size());
    }

    @CallerSideMethod
    public ISessionStorage getSessionStorage() {
        return getActor().sessionStorage;
    }

    /**
     * reply a request catched by interceptor, note this is server dependent and bound to undertow.
     * for servlet containers, just override KontraktorServlet methods
     * @param exchange
     */
    public void handleDirectRequest(HttpServerExchange exchange) {
        Log.Info(this,"direct request received "+exchange);
        getDirectRequestResponse(exchange.getRequestPath()).then( (s,err) -> {
            exchange.setResponseCode(200);
            exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, "text/html; charset=utf-8");
            exchange.getResponseSender().send(s == null  ? ""+err : s );
        });
    }

    /**
     * simplified, override handleDirectRequest() for full control+access to http header and response type
     * @param path
     * @return
     */
    protected IPromise getDirectRequestResponse(String path) {
        return new Promise("Hey there "+path);
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy