B4J Question Jetty 9.4 and hazelcast session management

mindful

Active Member
Licensed User
Hello, in the early version (9.3) of Jetty i was using hazelcast as session storage but now as jetty session management has been rewritten i ecountered a small issue. I am trying to implement with inline java but the source has lambdas and b4j can't compile. Can anyone help me to do this without lambdas?

B4X:
import com.hazelcast.core.IMap;
import org.eclipse.jetty.server.session.AbstractSessionDataStore;
import org.eclipse.jetty.server.session.SessionContext;
import org.eclipse.jetty.server.session.SessionData;
import org.eclipse.jetty.server.session.SessionDataStore;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;

import java.util.Collections;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
* Session data stored in Hazelcast
*/
@ManagedObject
public class HazelcastSessionDataStore
    extends AbstractSessionDataStore
    implements SessionDataStore
{

    private  final static Logger LOG = Log.getLogger( "org.eclipse.jetty.server.session");

    private IMap<String, SessionData> sessionDataMap;

    public HazelcastSessionDataStore()
    {
        // no op
    }

    @Override
    public SessionData load( String id )
        throws Exception
    {

        final AtomicReference<SessionData> reference = new AtomicReference<SessionData>();
        final AtomicReference<Exception> exception = new AtomicReference<Exception>();

        //ensure the load runs in the context classloader scope
        _context.run( () -> {
            try
            {
                if (LOG.isDebugEnabled())
                {
                    LOG.debug( "Loading session {} from hazelcast", id );
                }
                SessionData sd = sessionDataMap.get( getCacheKey( id ) );
                reference.set(sd);
            }
            catch (Exception e)
            {
                exception.set(e);
            }
        } );

        if (exception.get() != null)
        {
            throw exception.get();
        }
        return reference.get();
    }

    @Override
    public boolean delete( String id )
        throws Exception
    {
        return sessionDataMap == null ? false : sessionDataMap.remove( getCacheKey( id ) ) != null;
    }

    public IMap<String, SessionData> getSessionDataMap()
    {
        return sessionDataMap;
    }

    public void setSessionDataMap( IMap<String, SessionData> sessionDataMap )
    {
        this.sessionDataMap = sessionDataMap;
    }

    @Override
    public void initialize( SessionContext context )
        throws Exception
    {
        _context = context;
    }

    @Override
    public void doStore( String id, SessionData data, long lastSaveTime )
        throws Exception
    {
        this.sessionDataMap.set( getCacheKey( id ), data);
    }

    @Override
    public boolean isPassivating()
    {
        return true;
    }

    @Override
    public Set<String> doGetExpired( Set<String> candidates )
    {
        if (candidates == null || candidates.isEmpty())
        {
            return Collections.emptySet();
        }
        long now = System.currentTimeMillis();
        return candidates.stream().filter( candidate -> {
            if (LOG.isDebugEnabled())
            {
                LOG.debug( "Checking expiry for candidate {}", candidate );
            }
            try
            {
                SessionData sd = load(candidate);

                //if the session no longer exists
                if (sd == null)
                {
                    if (LOG.isDebugEnabled())
                    {
                        LOG.debug( "Session {} does not exist in Hazelcast", candidate );
                    }
                    return true;
                }
                else
                {
                    if (_context.getWorkerName().equals(sd.getLastNode()))
                    {
                        //we are its manager, add it to the expired set if it is expired now
                        if ((sd.getExpiry() > 0 ) && sd.getExpiry() <= now)
                        {
                            if (LOG.isDebugEnabled())
                            {
                                LOG.debug( "Session {} managed by {} is expired", candidate, _context.getWorkerName() );
                            }
                            return true;
                        }
                    }
                    else
                    {
                        //if we are not the session's manager, only expire it iff:
                        // this is our first expiryCheck and the session expired a long time ago
                        //or
                        //the session expired at least one graceperiod ago
                        if (_lastExpiryCheckTime <=0)
                        {
                            if ((sd.getExpiry() > 0 ) && sd.getExpiry() < (now - (1000L * (3 * _gracePeriodSec))))
                            {
                                return true;
                            }
                        }
                        else
                        {
                            if ((sd.getExpiry() > 0 ) && sd.getExpiry() < (now - (1000L * _gracePeriodSec)))
                            {
                                return true;
                            }
                        }
                    }
                }
            }
            catch (Exception e)
            {
                LOG.warn("Error checking if candidate {} is expired so expire it", candidate, e);
                return true;
            }
            return false;
        } ).collect( Collectors.toSet() );
    }

    @Override
    public boolean exists( String id )
        throws Exception
    {
        return this.sessionDataMap.containsKey( getCacheKey( id ) );
    }

    public String getCacheKey( String id )
    {
        return _context.getCanonicalContextPath() + "_" + _context.getVhost() + "_" + id;
    }
}


as you can see in the load method there is:
//ensure the load runs in the context classloader scope
_context.run( () -> {


Thanks in advance !
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
You can replace it with:
B4X:
_context.run( new Runnable() {
       
       @Override
       public void run() {
         
try
 {
 if (LOG.isDebugEnabled())
 {
 LOG.debug( "Loading session {} from hazelcast", id );
 }
 SessionData sd = sessionDataMap.get( getCacheKey( id ) );
 reference.set(sd);
 }
 catch (Exception e)
 {
 exception.set(e);
 }
         
       }
     });
 
Upvote 0

mindful

Active Member
Licensed User
thank you but I found that the source contains also other lambda

B4X:
    @Override
    public Set<String> doGetExpired( Set<String> candidates )
    {
        if (candidates == null || candidates.isEmpty())
        {
            return Collections.emptySet();
        }
        long now = System.currentTimeMillis();
        return candidates.stream().filter( candidate -> {
            if (LOG.isDebugEnabled())
            {
                LOG.debug( "Checking expiry for candidate {}", candidate );
            }
            try
            {
                SessionData sd = load(candidate);

                //if the session no longer exists
                if (sd == null)
                {
                    if (LOG.isDebugEnabled())
                    {
                        LOG.debug( "Session {} does not exist in Hazelcast", candidate );
                    }
                    return true;
                }
                else
                {
                    if (_context.getWorkerName().equals(sd.getLastNode()))
                    {
                        //we are its manager, add it to the expired set if it is expired now
                        if ((sd.getExpiry() > 0 ) && sd.getExpiry() <= now)
                        {
                            if (LOG.isDebugEnabled())
                            {
                                LOG.debug( "Session {} managed by {} is expired", candidate, _context.getWorkerName() );
                            }
                            return true;
                        }
                    }
                    else
                    {
                        //if we are not the session's manager, only expire it iff:
                        // this is our first expiryCheck and the session expired a long time ago
                        //or
                        //the session expired at least one graceperiod ago
                        if (_lastExpiryCheckTime <=0)
                        {
                            if ((sd.getExpiry() > 0 ) && sd.getExpiry() < (now - (1000L * (3 * _gracePeriodSec))))
                            {
                                return true;
                            }
                        }
                        else
                        {
                            if ((sd.getExpiry() > 0 ) && sd.getExpiry() < (now - (1000L * _gracePeriodSec)))
                            {
                                return true;
                            }
                        }
                    }
                }
            }
            catch (Exception e)
            {
                LOG.warn("Error checking if candidate {} is expired so expire it", candidate, e);
                return true;
            }
            return false;
        } ).collect( Collectors.toSet() );
    }

Can I add this class to a project in eclipse, build a jar and add it as an Additional jar in b4j and continue using the source as it is ?
 
Upvote 0

mindful

Active Member
Licensed User
@alwaysbusy I don't think it's usefull for ABMaterial, as what I am trying to do is run multiple instances of the same app behind a load balancer and as all the client (user browser) details are saved in the session I need to replicate/distribute those sessions in each instance of the app and for this I am using Hazelcast (in memory distributed database). So in early version of jetty (9.3) all that was possible with some extra jars and little server config, but as session magament changed in jetty 9.4 the hazelcast session managent jars aren't working.... I guess I have to eventually learn lambdas (never used them or needed them :) )
 
Upvote 0

mindful

Active Member
Licensed User
@Erel ... I just installed Eclipse, started a project, added those classes and managed to export a jar but some error raises:
B4X:
error: unreported exception Exception; must be caught or declared to be thrown
    HazelcastSessionDataStore hazelcastSessionDataStore = (HazelcastSessionDataStore) hazelcastSessionDataStoreFactory.getSessionDataStore( context.getSessionHandler() );

I have uploaded the eclipse project and the exported jar.

the code I am using is with inline java:
B4X:
import org.eclipse.jetty.server.session.DefaultSessionCache;
import org.eclipse.jetty.server.session.SessionContext;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.hazelcast.session.HazelcastSessionDataStoreFactory;
import org.eclipse.jetty.hazelcast.session.HazelcastSessionDataStore;

public void initializeSessionStore(ServletContextHandler context, HazelcastInstance hazelcastInstance) {
    SessionContext sessionContext = new SessionContext( "foo", null );

    HazelcastSessionDataStoreFactory hazelcastSessionDataStoreFactory = new HazelcastSessionDataStoreFactory();
    hazelcastSessionDataStoreFactory.setHazelcastInstance(hazelcastInstance);
  
    HazelcastSessionDataStore hazelcastSessionDataStore = (HazelcastSessionDataStore) hazelcastSessionDataStoreFactory.getSessionDataStore( context.getSessionHandler() );
    hazelcastSessionDataStore.initialize( sessionContext );

    DefaultSessionCache defaultSessionCache = new DefaultSessionCache( context.getSessionHandler() );
    defaultSessionCache.setSessionDataStore( hazelcastSessionDataStore );
    context.getSessionHandler().setSessionCache( defaultSessionCache );

}

Maybe someone finds a little time to look where is the problem :)
 

Attachments

  • hazelcast-jetty9.4-sessionmanager.jar
    8.9 KB · Views: 250
  • HazelcastSessionDataStore.zip
    11.9 KB · Views: 240
Upvote 0

mindful

Active Member
Licensed User
So it works using the jar created in eclipse that contains the 2 class files but the inline code i used needed a change (Fix provided by @Daestrum) :

Quick first thoughts, try adding
B4X:
import java.lang.Exception
and replace
B4X:
public void initializeSessionStore(ServletContextHandler context, HazelcastInstance hazelcastInstance) {
with
B4X:
public void initializeSessionStore(ServletContextHandler context, HazelcastInstance hazelcastInstance) throws Exception{

Many thanks !
 
Upvote 0
Top