Package org.filemq

Source Code of org.filemq.FmqServer$ServerThread

/*  =========================================================================
    fmq_server.c

    Generated class for fmq_server protocol server
    -------------------------------------------------------------------------
    Copyright (c) 1991-2012 iMatix Corporation -- http://www.imatix.com    
    Copyright other contributors as noted in the AUTHORS file.             
                                                                           
    This file is part of FILEMQ, see http://filemq.org.                    
                                                                           
    This is free software; you can redistribute it and/or modify it under  
    the terms of the GNU Lesser General Public License as published by the 
    Free Software Foundation; either version 3 of the License, or (at your 
    option) any later version.                                             
                                                                           
    This software is distributed in the hope that it will be useful, but   
    WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTA-  
    BILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General 
    Public License for more details.                                       
                                                                           
    You should have received a copy of the GNU Lesser General Public License
    along with this program. If not, see http://www.gnu.org/licenses/.     
    =========================================================================
*/

package org.filemq;

import java.util.List;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;

import org.zeromq.ZMQ;
import org.zeromq.ZMQ.Socket;
import org.zeromq.ZMQ.Poller;
import org.zeromq.ZThread;
import org.zeromq.ZMsg;
import org.zeromq.ZContext;
import org.zeromq.ZFrame;



//  ---------------------------------------------------------------------
//  Structure of our front-end API class

public class FmqServer {

    private ZContext ctx;        //  CZMQ context
    private Socket pipe;         //  Pipe through to server


    //  --------------------------------------------------------------------------
    //  Create a new fmq_server and a new server instance

    public FmqServer ()
    {
        ctx = new ZContext ();
        pipe = ZThread.fork (ctx, new ServerThread ());
    }


    //  --------------------------------------------------------------------------
    //  Destroy the fmq_server and stop the server

    public void destroy ()
    {
        pipe.send ("STOP");
        pipe.recvStr ();
        ctx.destroy ();
    }


    //  --------------------------------------------------------------------------
    //  Load server configuration data
    public void configure (final String config_file)
    {
        pipe.sendMore ("CONFIG");
        pipe.send (config_file);
    }


    //  --------------------------------------------------------------------------
    //  Set one configuration key value

    public void setoption (final String path, final String value)
    {
        pipe.sendMore ("SETOPTION");
        pipe.sendMore (path);
        pipe.send (value);
    }

    //  --------------------------------------------------------------------------

    public int bind (final String endpoint)
    {
        assert (endpoint != null);
        pipe.sendMore ("BIND");
        pipe.send (String.format("%s", endpoint));
        String reply = pipe.recvStr ();
        int rc = Integer.parseInt (reply);
        return rc;
    }

    //  --------------------------------------------------------------------------

    public void publish (final String location,final String alias)
    {
        assert (location != null);
        assert (alias != null);
        pipe.sendMore ("PUBLISH");
        pipe.sendMore (String.format("%s", location));
        pipe.send (String.format("%s", alias));
    }

    //  --------------------------------------------------------------------------

    public void setAnonymous (long enabled)
    {
        pipe.sendMore ("SET ANONYMOUS");
        pipe.send (String.format("%d", enabled));
    }



    //  ---------------------------------------------------------------------
    //  State machine constants

    private enum State {
        start_state (1),
        checking_client_state (2),
        challenging_client_state (3),
        ready_state (4),
        dispatching_state (5);

        @SuppressWarnings ("unused")
        private final int state;
        State (int state)
        {
            this.state = state;
        }
    };

    private enum Event {
        terminate_event (-1),
        ohai_event (1),
        heartbeat_event (2),
        expired_event (3),
        _other_event (4),
        friend_event (5),
        foe_event (6),
        maybe_event (7),
        yarly_event (8),
        icanhaz_event (9),
        nom_event (10),
        hugz_event (11),
        kthxbai_event (12),
        dispatch_event (13),
        send_chunk_event (14),
        send_delete_event (15),
        next_patch_event (16),
        no_credit_event (17),
        finished_event (18);

        @SuppressWarnings ("unused")
        private final int event;
        Event (int event)
        {
            this.event = event;
        }
    };


    //  There's no point making these configurable
    private static final int CHUNK_SIZE = 1000000;

    //  --------------------------------------------------------------------------     
    //  Subscription object                                                            
                                                                                       
    private static class Sub {                                                         
        private Client client;                //  Always refers to live client         
        private String path;                  //  Path client is subscribed to         
        private Map <String, String> cache;   //  Client's cache list                  
                                                                                       
        //  -------------------------------------------------------------------------- 
        //  Constructor                                                                
                                                                                       
        private Sub (Client client, String path, Map <String, String> cache)           
        {                                                                              
            this.client = client;                                                      
            this.path = path;                                                          
            this.cache = new HashMap <String, String> (cache);                         
            resolveCachePath (this.cache, this);                                       
        }                                                                              
                                                                                       
        //  Cached filenames may be local, in which case prefix them with              
        //  the subscription path so we can do a consistent match.                     
                                                                                       
        private static void resolveCachePath (Map <String, String> cache, Sub self)    
        {                                                                              
            for (String key : cache.keySet ()) {                                       
              if (!key.startsWith ("/")) {                                             
                  String new_key = String.format ("%s/%s", self.path, key);            
                  cache.put (new_key, cache.remove (key));                             
              }                                                                        
            }                                                                          
        }                                                                              
                                                                                       
                                                                                       
        //  -------------------------------------------------------------------------- 
        //  Destructor                                                                 
                                                                                       
        private void destroy ()                                                        
        {                                                                              
        }                                                                              
                                                                                       
                                                                                       
        //  -------------------------------------------------------------------------- 
        //  Add patch to sub client patches list                                       
                                                                                       
        private void addPatch (FmqPatch patch)                                         
        {                                                                              
            //  Skip file creation if client already has identical file                
            patch.setDigest ();                                                        
            if (patch.op () == FmqPatch.OP.patch_create) {                             
                String digest = cache.get (patch.virtual ());                          
                if (digest != null && digest.compareToIgnoreCase (patch.digest ()) == 0)
                    return;             //  Just skip patch for this client            
            }                                                                          
            //  Remove any previous patches for the same file                          
            Iterator <FmqPatch> it = client.patches.iterator ();                       
            while (it.hasNext ()) {                                                    
                FmqPatch existing = it.next ();                                        
                if (patch.virtual ().equals (existing.virtual ())) {                   
                    it.remove ();                                                      
                    existing.destroy ();                                               
                    break;                                                             
                }                                                                      
            }                                                                          
            //  Track that we've queued patch for client, so we don't do it twice      
            cache.put (patch.digest (), patch.virtual ());                             
            client.patches.add (patch.dup ());                                         
        }                                                                              
    }                                                                                  

    //  --------------------------------------------------------------------------   
    //  Mount point in memory                                                        
                                                                                     
    private static class Mount {                                                     
        private String location;        //  Physical location                        
        private String alias;           //  Alias into our tree                      
        private FmqDir dir;             //  Directory snapshot                       
        private List <Sub> subs;  //  Client subscriptions                           
                                                                                     
                                                                                     
        //  --------------------------------------------------------------------------
        //  Constructor                                                              
        //  Loads directory tree if possible                                         
                                                                                     
        private Mount (String location, String alias)                                
        {                                                                            
            //  Mount path must start with '/'                                       
            //  We'll do better error handling later                                 
            assert (alias.startsWith ("/"));                                         
                                                                                     
            this.location = location;                                                
            this.alias = alias;                                                      
            dir = FmqDir.newFmqDir (location, null);                                 
            subs = new ArrayList <Sub> ();                                           
        }                                                                            
                                                                                     
                                                                                     
        //  --------------------------------------------------------------------------
        //  Destructor                                                               
                                                                                     
        private void destroy ()                                                      
        {                                                                            
            //  Destroy subscriptions                                                
            for (Sub sub : subs) {                                                   
                sub.destroy ();                                                      
            }                                                                        
            if (dir != null)                                                         
                dir.destroy ();                                                      
        }                                                                            
                                                                                     
                                                                                     
        //  --------------------------------------------------------------------------
        //  Reloads directory tree and returns true if activity, false if the same   
                                                                                     
        private boolean refresh (Server server)                                      
        {                                                                            
            boolean activity = false;                                                
                                                                                     
            //  Get latest snapshot and build a patches list for any changes         
            FmqDir latest = FmqDir.newFmqDir (location, null);                       
            List <FmqPatch> patches = FmqDir.diff (dir, latest, alias);              
                                                                                     
            //  Drop old directory and replace with latest version                   
            if (dir != null)                                                         
                dir.destroy ();                                                      
            dir = latest;                                                            
                                                                                     
            //  Copy new patches to clients' patches list                            
            for (Sub sub : subs) {                                                   
                for (FmqPatch patch : patches) {                                     
                    sub.addPatch (patch);                                            
                    activity = true;                                                 
                }                                                                    
            }                                                                        
                                                                                     
            //  Destroy patches, they've all been copied                             
            for (FmqPatch patch : patches) {                                         
                patch.destroy ();                                                    
            }                                                                        
            return activity;                                                         
        }                                                                            
                                                                                     
                                                                                     
        //  --------------------------------------------------------------------------
        //  Store subscription for mount point                                       
                                                                                     
        private void storeSub (Client client, FmqMsg request)                        
        {                                                                            
            //  Store subscription along with any previous ones                      
            //  Coalesce subscriptions that are on same path                         
            String path = request.path ();                                           
            Iterator <Sub> it = subs.iterator ();                                    
            while (it.hasNext ()) {                                                  
                Sub sub = it.next ();                                                
                if (client == sub.client) {                                          
                    //  If old subscription is superset/same as new, ignore new      
                    if (path.startsWith (sub.path))                                  
                        return;                                                      
                    else                                                             
                    //  If new subscription is superset of old one, remove old       
                    if (sub.path.startsWith (path)) {                                
                        it.remove ();                                                
                        sub.destroy ();                                              
                    }                                                                
                }                                                                    
            }                                                                        
            //  New subscription for this client, append to our list                 
            Sub sub = new Sub (client, path, request.cache ());                      
            subs.add (sub);                                                          
                                                                                     
            //  If client requested resync, send full mount contents now             
            if (request.optionsNumber ("RESYNC", 0) == 1) {                          
                List <FmqPatch> patches = FmqDir.resync (dir, alias);                
                for (FmqPatch patch : patches) {                                     
                    sub.addPatch (patch);                                            
                    patch.destroy ();                                                
                }                                                                    
            }                                                                        
        }                                                                            
                                                                                     
                                                                                     
        //  --------------------------------------------------------------------------
        //  Purge subscriptions for a specified client                               
                                                                                     
        private void purgeSub (Client client)                                        
        {                                                                            
            Iterator <Sub> it = subs.iterator ();                                    
            while (it.hasNext ()) {                                                  
                Sub sub = it.next ();                                                
                if (sub.client == client) {                                          
                    it.remove ();                                                    
                    sub.destroy ();                                                  
                }                                                                    
            }                                                                        
        }                                                                            
    }                                                                                

    //  Client hash function that checks if client is alive                        
    private static void clientDispatch (Map <String, Client> clients, Server server)
    {                                                                              
        for (Client client : clients.values ())                                    
            server.clientExecute (client, Event.dispatch_event);                   
    }                                                                              


    //  ---------------------------------------------------------------------
    //  Context for each client connection

    private static class Client {
        //  Properties accessible to client actions
        private long heartbeat;          //  Heartbeat interval
        private Event next_event;         //  Next event
        private int credit;                     //  Credit remaining          
        private List <FmqPatch> patches;  //  Patches to send                 
        private FmqPatch patch;                 //  Current patch             
        private FmqFile file;                   //  Current file we're sending
        private long offset;                    //  Offset of next read in file
        private long sequence;                  //  Sequence number for chunk 

        //  Properties you should NOT touch
        private Socket router;                  //  Socket to client
        private long heartbeat_at;              //  Next heartbeat at this time
        private long expires_at;                //  Expires at this time
        private State state;                    //  Current state
        private Event event;                    //  Current event
        private String hashkey;                 //  Key into clients hash
        private ZFrame address;                 //  Client address identity
        private FmqMsg request;                 //  Last received request
        private FmqMsg reply;                   //  Reply to send out, if any


        //  Client methods

        private Client (String hashkey, ZFrame address)
        {
            this.hashkey = hashkey;
            this.state = State.start_state;
            this.address = address.duplicate ();
            reply = new FmqMsg (0);
            reply.setAddress (this.address);
            patches = new ArrayList <FmqPatch> ();
        }

        private void destroy ()
        {
            if (address != null)
                address.destroy ();
            if (request != null)
                request.destroy ();
            if (reply != null)
                reply.destroy ();
            for (FmqPatch patch : patches)
                patch.destroy ();        
        }

        private void setRequest (FmqMsg request)
        {
            if (this.request != null)
                this.request.destroy ();
            this.request = request;

            //  Any input from client counts as heartbeat
            heartbeat_at = System.currentTimeMillis () + heartbeat;
            //  Any input from client counts as activity
            expires_at = System.currentTimeMillis () + heartbeat * 3;
        }
    }

    //  Client hash function that calculates tickless timer
    private static long clientTickless (Map <String, Client> clients , long tickless)
    {
        for (Client self: clients.values ()) {
            if (tickless > self.heartbeat_at)
                tickless = self.heartbeat_at;
        }
        return tickless;
    }

    //  Client hash function that checks if client is alive
    private static void clientPing (Map <String, Client> clients , Server server)
    {
        Iterator <Map.Entry <String, Client>> it = clients.entrySet ().iterator ();
        while (it.hasNext ()) {
            Client self = it.next ().getValue ();
            //  Expire client if it's not answered us in a while
            if (System.currentTimeMillis () >= self.expires_at && self.expires_at > 0) {
                //  In case dialog doesn't handle expired_event by destroying
                //  client, set expires_at to zero to prevent busy looping
                self.expires_at = 0;
                if (server.clientExecute (self, Event.expired_event, true))
                    it.remove ();
            }
            else
            //  Check whether to send heartbeat to client
            if (System.currentTimeMillis () >= self.heartbeat_at) {
                server.clientExecute (self, Event.heartbeat_event);
                self.heartbeat_at = System.currentTimeMillis () + self.heartbeat;
            }
        }
    }

    //  ---------------------------------------------------------------------
    //  Context for the server thread

    private static class Server {
        //  Properties accessible to client actions
        private List <Mount> mounts;  //  Mount points    
        private int port;                   //  Server port

        //  Properties you should NOT touch
        private ZContext ctx;                   //  Own CZMQ context
        private Socket pipe;                    //  Socket to back to caller
        private Socket router;                  //  Socket to talk to clients
        private Map <String, Client> clients;   //  Clients we've connected to
        private boolean stopped;                //  Has server stopped?
        private FmqConfig config;               //  Configuration tree
        private int monitor;                    //  Monitor interval
        private long monitor_at;                //  Next monitor at this time
        private int heartbeat;                  //  Heartbeat for clients

        //  Server methods
        private void config ()
        {
            //  Get standard server configuration
            monitor = Integer.parseInt (
                config.resolve ("server/monitor", "1")) * 1000;
            heartbeat = Integer.parseInt (
                config.resolve ("server/heartbeat", "1")) * 1000;
            monitor_at = System.currentTimeMillis () + monitor;
        }

        private Server (ZContext ctx, Socket pipe)
        {
            this.ctx = ctx;
            this.pipe = pipe;
            router = ctx.createSocket (ZMQ.ROUTER);
            clients = new HashMap <String, Client> ();
            config = new FmqConfig ("root", null);
            config ();
            mounts = new ArrayList <Mount> ();
        }

        private void destroy ()
        {
            ctx.destroySocket (router);
            config.destroy ();
            for (Client c: clients.values ())
                c.destroy ();
            //  Destroy mount points 
            for (Mount mount : mounts)
                mount.destroy ();    
        }

        //  Apply configuration tree:
        //   * apply server configuration
        //   * print any echo items in top-level sections
        //   * apply sections that match methods

        private void applyConfig ()
        {
            //  Apply echo commands and class methods
            FmqConfig section = config.child ();
            while (section != null) {
                FmqConfig entry = section.child ();
                while (entry != null) {
                    if (entry.name ().equals ("echo"))
                        zclock_log (entry.value ());
                    entry = entry.next ();
                }
                if (section.name ().equals ("bind")) {
                    String endpoint = section.resolve ("endpoint", "?");
                    port = bind (router, endpoint);
                }
                else
                if (section.name ().equals ("publish")) {
                    String location = section.resolve ("location", "?");
                    String alias = section.resolve ("alias", "?");
                    Mount mount = new Mount (location, alias);
                    mounts.add (mount);                      
                }
                else
                if (section.name ().equals ("set_anonymous")) {
                    long enabled = Long.parseLong (section.resolve ("enabled", ""));
                    //  Enable anonymous access without a config file            
                    config.setPath ("security/anonymous", enabled > 0 ? "1" :"0");
                }
                section = section.next ();
            }
            config ();
        }

        private void controlMessage ()
        {
            ZMsg msg = ZMsg.recvMsg (pipe);
            String method = msg.popString ();
            if (method.equals ("BIND")) {
                String endpoint = msg.popString ();
                port = bind (router, endpoint);
                pipe.send (String.format ("%d", port));
            }
            else
            if (method.equals ("PUBLISH")) {
                String location = msg.popString ();
                String alias = msg.popString ();
                Mount mount = new Mount (location, alias);
                mounts.add (mount);                      
            }
            else
            if (method.equals ("SET ANONYMOUS")) {
                long enabled = Long.parseLong (msg.popString ());
                //  Enable anonymous access without a config file            
                config.setPath ("security/anonymous", enabled > 0 ? "1" :"0");
            }
            else
            if (method.equals ("CONFIG")) {
                String config_file = msg.popString ();
                config.destroy ();
                config = FmqConfig.load (config_file);
                if (config != null)
                    applyConfig ();
                else {
                    System.out.printf ("E: cannot load config file '%s'\n", config_file);
                    config = new FmqConfig ("root", null);
                }
            }
            else
            if (method.equals ("SETOPTION")) {
                String path = msg.popString ();
                String value = msg.popString ();
                config.setPath (path, value);
                config ();
            }
            else
            if (method.equals ("STOP")) {
                pipe.send ("OK");
                stopped = true;
            }
            msg.destroy ();
            msg = null;
        }

        //  Custom actions for state machine

        private void terminateTheClient (Client client)
        {
            for (Mount mount : mounts) {             
                mount.purgeSub (client);             
            }                                        
            client.next_event = Event.terminate_event;
        }

        private void tryAnonymousAccess (Client client)
        {
            if (Integer.parseInt (config.resolve ("security/anonymous", "0")) == 1)
                client.next_event = Event.friend_event;                           
            else                                                                  
            if (Integer.parseInt (config.resolve ("security/plain", "0")) == 1)   
                client.next_event = Event.maybe_event;                            
            else                                                                  
                client.next_event = Event.foe_event;                              
        }

        private void listSecurityMechanisms (Client client)
        {
            if (Integer.parseInt (config.resolve ("security/anonymous", "0")) == 1)
                client.reply.appendMechanisms ("ANONYMOUS");                      
            if (Integer.parseInt (config.resolve ("security/plain", "0")) == 1)   
                client.reply.appendMechanisms ("PLAIN");                          
        }

        private void trySecurityMechanism (Client client)
        {
            client.next_event = Event.foe_event;                            
            String [] result = new String [2];                              
            String login, password;                                         
            if (client.request.mechanism ().equals ("PLAIN")                
            && FmqSasl.plainDecode (client.request.response (), result)) {  
                login = result [0];                                         
                password = result [1];                                      
                FmqConfig account = config.locate ("security/plain/account");
                while (account != null) {                                   
                    if (account.resolve ("login", "").equals (login)        
                    && account.resolve ("password", "").equals (password)) {
                        client.next_event = Event.friend_event;             
                        break;                                              
                    }                                                       
                    account = account.next ();                              
                }                                                           
            }                                                               
        }

        private void storeClientSubscription (Client client)
        {
            //  Find mount point with longest match to subscription  
            String path = client.request.path ();                    
                                                                     
            Mount mount = mounts.isEmpty () ? null : mounts.get (0)
            for (Mount check : mounts) {                             
                //  If check->alias is prefix of path and alias is   
                //  longer than current mount then we have a new mount
                if (path.startsWith (check.alias)                    
                &&  check.alias.length () > mount.alias.length ())   
                    mount = check;                                   
            }                                                        
            mount.storeSub (client, client.request);                 
        }

        private void storeClientCredit (Client client)
        {
            client.credit += client.request.credit ();
        }

        private void monitorTheServer (Client client)
        {
            boolean activity = false;         
            for (Mount mount : mounts) {      
                if (mount.refresh (this))     
                    activity = true;          
            }                                 
            if (activity)                     
                clientDispatch (clients, this);
        }

        private void getNextPatchForClient (Client client)
        {
            //  Get next patch for client if we're not doing one already                   
            if (client.patch == null)                                                      
                client.patch = client.patches.isEmpty () ? null : client.patches.remove (0);
            if (client.patch == null) {                                                    
                client.next_event = Event.finished_event;                                  
                return;                                                                    
            }                                                                              
            //  Get virtual filename from patch                                            
            client.reply.setFilename (client.patch.virtual ());                            
                                                                                           
            //  We can process a delete patch right away                                   
            if (client.patch.op () == FmqPatch.OP.patch_delete) {                          
                client.reply.setSequence (client.sequence++);                              
                client.reply.setOperation (FmqMsg.FMQ_MSG_FILE_DELETE);                    
                client.next_event = Event.send_delete_event;                               
                                                                                           
                //  No reliability in this version, assume patch delivered safely          
                client.patch.destroy ();                                                   
                client.patch = null;                                                       
            }                                                                              
            else                                                                           
            if (client.patch.op () == FmqPatch.OP.patch_create) {                          
                //  Create patch refers to file, open that for input if needed             
                if (client.file == null) {                                                 
                    client.file = client.patch.file ().dup ();                             
                    if (client.file.input () == false) {                                   
                        //  File no longer available, skip it                              
                        client.patch.destroy ();                                           
                        client.file.destroy ();                                            
                        client.patch = null;                                               
                        client.file = null;                                                
                        client.next_event = Event.next_patch_event;                        
                        return;                                                            
                    }                                                                      
                    client.offset = 0;                                                     
                }                                                                          
                //  Get next chunk for file                                                
                FmqChunk chunk = client.file.read (CHUNK_SIZE, client.offset);             
                assert (chunk != null);                                                    
                                                                                           
                //  Check if we have the credit to send chunk                              
                if (chunk.size () <= client.credit) {                                      
                    client.reply.setSequence (client.sequence++);                          
                    client.reply.setOperation (FmqMsg.FMQ_MSG_FILE_CREATE);                
                    client.reply.setOffset (client.offset);                                
                    client.reply.setChunk (new ZFrame (chunk.data ()));                    
                                                                                           
                    client.offset += chunk.size ();                                        
                    client.credit -= chunk.size ();                                        
                    client.next_event = Event.send_chunk_event;                            
                                                                                           
                    //  Zero-sized chunk means end of file                                 
                    if (chunk.size () == 0) {                                              
                        client.file.destroy ();                                            
                        client.patch.destroy ();                                           
                        client.patch = null;                                               
                        client.file = null;                                                
                    }                                                                      
                }                                                                          
                else                                                                       
                    client.next_event = Event.no_credit_event;                             
                                                                                           
                chunk.destroy ();                                                          
            }                                                                              
        }

        //  Execute state machine as long as we have events
        //  Returns true, if it requires remove on iteration

        private boolean clientExecute (Client client, Event event)
        {
            return clientExecute (client, event, false);
        }

        private boolean clientExecute (Client client, Event event, boolean oniter)
        {
            client.next_event = event;
            while (client.next_event != null) {
                client.event = client.next_event;
                client.next_event = null;
                switch (client.state) {
                case start_state:
                    if (client.event == Event.ohai_event) {
                        tryAnonymousAccess (client);
                        client.state = State.checking_client_state;
                    }
                    else
                    if (client.event == Event.heartbeat_event) {
                    }
                    else
                    if (client.event == Event.expired_event) {
                        terminateTheClient (client);
                    }
                    else {
                            //  Process all other events
                        client.reply.setId (FmqMsg.RTFM);
                        client.reply.send (client.router);
                        client.reply = new FmqMsg (0);
                        client.reply.setAddress (client.address);
                        terminateTheClient (client);
                    }
                    break;

                case checking_client_state:
                    if (client.event == Event.friend_event) {
                        client.reply.setId (FmqMsg.OHAI_OK);
                        client.reply.send (client.router);
                        client.reply = new FmqMsg (0);
                        client.reply.setAddress (client.address);
                        client.state = State.ready_state;
                    }
                    else
                    if (client.event == Event.foe_event) {
                        client.reply.setId (FmqMsg.SRSLY);
                        client.reply.send (client.router);
                        client.reply = new FmqMsg (0);
                        client.reply.setAddress (client.address);
                        terminateTheClient (client);
                    }
                    else
                    if (client.event == Event.maybe_event) {
                        listSecurityMechanisms (client);
                        client.reply.setId (FmqMsg.ORLY);
                        client.reply.send (client.router);
                        client.reply = new FmqMsg (0);
                        client.reply.setAddress (client.address);
                        client.state = State.challenging_client_state;
                    }
                    else
                    if (client.event == Event.heartbeat_event) {
                    }
                    else
                    if (client.event == Event.expired_event) {
                        terminateTheClient (client);
                    }
                    else
                    if (client.event == Event.ohai_event) {
                        tryAnonymousAccess (client);
                        client.state = State.checking_client_state;
                    }
                    else {
                            //  Process all other events
                        client.reply.setId (FmqMsg.RTFM);
                        client.reply.send (client.router);
                        client.reply = new FmqMsg (0);
                        client.reply.setAddress (client.address);
                        terminateTheClient (client);
                    }
                    break;

                case challenging_client_state:
                    if (client.event == Event.yarly_event) {
                        trySecurityMechanism (client);
                        client.state = State.checking_client_state;
                    }
                    else
                    if (client.event == Event.heartbeat_event) {
                    }
                    else
                    if (client.event == Event.expired_event) {
                        terminateTheClient (client);
                    }
                    else
                    if (client.event == Event.ohai_event) {
                        tryAnonymousAccess (client);
                        client.state = State.checking_client_state;
                    }
                    else {
                            //  Process all other events
                        client.reply.setId (FmqMsg.RTFM);
                        client.reply.send (client.router);
                        client.reply = new FmqMsg (0);
                        client.reply.setAddress (client.address);
                        terminateTheClient (client);
                    }
                    break;

                case ready_state:
                    if (client.event == Event.icanhaz_event) {
                        storeClientSubscription (client);
                        client.reply.setId (FmqMsg.ICANHAZ_OK);
                        client.reply.send (client.router);
                        client.reply = new FmqMsg (0);
                        client.reply.setAddress (client.address);
                    }
                    else
                    if (client.event == Event.nom_event) {
                        storeClientCredit (client);
                        getNextPatchForClient (client);
                        client.state = State.dispatching_state;
                    }
                    else
                    if (client.event == Event.hugz_event) {
                        client.reply.setId (FmqMsg.HUGZ_OK);
                        client.reply.send (client.router);
                        client.reply = new FmqMsg (0);
                        client.reply.setAddress (client.address);
                    }
                    else
                    if (client.event == Event.kthxbai_event) {
                        terminateTheClient (client);
                    }
                    else
                    if (client.event == Event.dispatch_event) {
                        getNextPatchForClient (client);
                        client.state = State.dispatching_state;
                    }
                    else
                    if (client.event == Event.heartbeat_event) {
                        client.reply.setId (FmqMsg.HUGZ);
                        client.reply.send (client.router);
                        client.reply = new FmqMsg (0);
                        client.reply.setAddress (client.address);
                    }
                    else
                    if (client.event == Event.expired_event) {
                        terminateTheClient (client);
                    }
                    else
                    if (client.event == Event.ohai_event) {
                        tryAnonymousAccess (client);
                        client.state = State.checking_client_state;
                    }
                    else {
                            //  Process all other events
                        client.reply.setId (FmqMsg.RTFM);
                        client.reply.send (client.router);
                        client.reply = new FmqMsg (0);
                        client.reply.setAddress (client.address);
                        terminateTheClient (client);
                    }
                    break;

                case dispatching_state:
                    if (client.event == Event.send_chunk_event) {
                        client.reply.setId (FmqMsg.CHEEZBURGER);
                        client.reply.send (client.router);
                        client.reply = new FmqMsg (0);
                        client.reply.setAddress (client.address);
                        getNextPatchForClient (client);
                    }
                    else
                    if (client.event == Event.send_delete_event) {
                        client.reply.setId (FmqMsg.CHEEZBURGER);
                        client.reply.send (client.router);
                        client.reply = new FmqMsg (0);
                        client.reply.setAddress (client.address);
                        getNextPatchForClient (client);
                    }
                    else
                    if (client.event == Event.next_patch_event) {
                        getNextPatchForClient (client);
                    }
                    else
                    if (client.event == Event.no_credit_event) {
                        client.state = State.ready_state;
                    }
                    else
                    if (client.event == Event.finished_event) {
                        client.state = State.ready_state;
                    }
                    else
                    if (client.event == Event.heartbeat_event) {
                    }
                    else
                    if (client.event == Event.expired_event) {
                        terminateTheClient (client);
                    }
                    else
                    if (client.event == Event.ohai_event) {
                        tryAnonymousAccess (client);
                        client.state = State.checking_client_state;
                    }
                    else {
                            //  Process all other events
                        client.reply.setId (FmqMsg.RTFM);
                        client.reply.send (client.router);
                        client.reply = new FmqMsg (0);
                        client.reply.setAddress (client.address);
                        terminateTheClient (client);
                    }
                    break;

                }
                if (client.next_event == Event.terminate_event) {
                    //  Automatically calls client_destroy
                    client.destroy ();
                    if (oniter)
                        return true;
                    clients.remove (client.hashkey);
                    break;
                }
            }
            return false;
        }

        private void clientMessage ()
        {
            FmqMsg request = FmqMsg.recv (router);
            if (request == null)
                return;         //  Interrupted; do nothing

            String hashkey = request.address ().strhex ();
            Client client = clients.get (hashkey);
            if (client == null) {
                client = new Client (hashkey, request.address ());
                client.heartbeat = heartbeat;
                client.router = router;
                clients.put (hashkey, client);
            }

            client.setRequest (request);
            if (request.id () == FmqMsg.OHAI)
                clientExecute (client, Event.ohai_event);
            else
            if (request.id () == FmqMsg.YARLY)
                clientExecute (client, Event.yarly_event);
            else
            if (request.id () == FmqMsg.ICANHAZ)
                clientExecute (client, Event.icanhaz_event);
            else
            if (request.id () == FmqMsg.NOM)
                clientExecute (client, Event.nom_event);
            else
            if (request.id () == FmqMsg.HUGZ)
                clientExecute (client, Event.hugz_event);
            else
            if (request.id () == FmqMsg.KTHXBAI)
                clientExecute (client, Event.kthxbai_event);
        }
    }

    //  The server runs as a background thread so that we can run multiple
    //  engines at once. The API talks to the server thread over an inproc
    //  pipe.

    //  Finally here's the server thread itself, which polls its two
    //  sockets and processes incoming messages

    private static class ServerThread
                          implements ZThread.IAttachedRunnable {
        @Override
        public void run (Object [] args, ZContext ctx, Socket pipe)
        {
            Server self = new Server (ctx, pipe);

            Poller items = ctx.getContext ().poller ();
            items.register (self.pipe, Poller.POLLIN);
            items.register (self.router, Poller.POLLIN);

            self.monitor_at = System.currentTimeMillis () + self.monitor;
            while (!self.stopped && !Thread.currentThread ().isInterrupted ()) {
                //  Calculate tickless timer, up to interval seconds
                long tickless = System.currentTimeMillis () + self.monitor;
                tickless = clientTickless (self.clients, tickless);

                //  Poll until at most next timer event
                long rc = items.poll (tickless - System.currentTimeMillis ());
                if (rc == -1)
                    break;              //  Context has been shut down

                //  Process incoming message from either socket
                if (items.pollin (0))
                    self.controlMessage ();

                if (items.pollin (1))
                    self.clientMessage ();

                //  Send heartbeats to idle clients as needed
                clientPing (self.clients, self);

                //  If clock went past timeout, then monitor server
                if (System.currentTimeMillis () >= self.monitor_at) {
                    self.monitorTheServer (null);
                    self.monitor_at = System.currentTimeMillis () + self.monitor;
                }
            }
            self.destroy ();
        }
    }

    public static void zclock_log (String fmt, Object ... args)
    {
        System.out.println (String.format (fmt, args));
    }

    public static int bind (Socket sock, String addr)
    {
        int pos = addr.lastIndexOf (':');
        String port = addr.substring (pos + 1);
        if (port.equals ("*"))
            return sock.bindToRandomPort (addr.substring (0, pos));
        else {
            sock.bind (addr);
            return Integer.parseInt (port);
        }
    }
}
TOP

Related Classes of org.filemq.FmqServer$ServerThread

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.