summaryrefslogtreecommitdiff
path: root/jsonrpc/src/main/java/com/orbekk/same/SameState.java
blob: 9638252a89be3e802154c1c85cb6cc7ae2354c9a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package com.orbekk.same;

import java.util.List;
import java.util.LinkedList;
import java.util.Map;
import java.util.HashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The implementation of a 'Same' state.
 *
 * This class manages the current state of the Same protocol.
 */
public class SameState extends Thread implements UrlReceiver {
    private Logger logger = LoggerFactory.getLogger(getClass());
    private ConnectionManager connections;
    private String currentState = "";
    private String networkName;

    /**
     * The master participant id.
     */
    private String masterId;

    /**
     * TODO: Remove.
     */
    public void setMasterId(String masterId) {
        this.masterId = masterId;
    }

    /**
     * The participants of this network.
     *
     * Maps clientId to url.
     */
    private Map<String, String> participants = new HashMap<String, String>();

    /**
     * The client id of this participant.
     */
    private String clientId;

    /**
     * Stopping condition for this thread.
     */
    private boolean stopped = false;

    private Map<String, String> pendingParticipants =
            new HashMap<String, String>();

    public SameState(String networkName, String clientId,
            ConnectionManager connections) {
        this.networkName = networkName;
        this.clientId = clientId;
        this.connections = connections;
        this.masterId = clientId;
        participants.put(clientId, null);
    }

    /**
     * Get participants as a list.
     *
     * List format: ["clientId1,url1", "clientId2,url2", ...]
     */
    public synchronized Map<String, String> getParticipants() {
        return participants;
    }

    /**
     * Reset this SameService to an initial state.
     *
     * TODO: Implement fully.
     */
    private synchronized void resetState() {
        pendingParticipants.clear();
    }

    public synchronized void joinNetwork(String networkName, String masterId,
            Map<String, String> participants) {
        resetState();
        this.networkName = networkName;
        this.masterId = masterId;
        this.participants = participants;
        logger.info("Joined network {}.", networkName);
    }

    public String getClientId() {
        return clientId;
    }

    public String getNetworkName() {
        return networkName;
    }

    public String getCurrentState() {
        return currentState;
    }

    public String getUrl() {
        return participants.get(clientId);
    }

    @Override
    public void setUrl(String url) {
        logger.info("My URL is {}", url);
        participants.put(clientId, url);
    }

    public synchronized void addParticipant(String clientId, String url) {
        logger.info("PendingParticipant.add: {} ({})", clientId, url);
        pendingParticipants.put(clientId, url);
        notifyAll();
    }

    private boolean isMaster() {
        return masterId.equals(clientId);
    }

    private synchronized void handleNewParticipants() {
        if (!isMaster()) {
            for (Map.Entry<String, String> e : pendingParticipants.entrySet()) {
                SameService master = connections.getConnection(
                        participants.get(masterId));
                logger.info("Redirecting participant request to {}", masterId);
                String clientId = e.getKey();
                String url = e.getValue();
                master.participateNetwork(networkName, clientId, url);
            }
        } else {
            participants.putAll(pendingParticipants);
            for (Map.Entry<String, String> e :
                    pendingParticipants.entrySet()) {
                String clientId = e.getKey();
                String url = e.getValue();
                logger.info("New participant: {} URL({})", clientId, url);
                SameService remoteService = connections.getConnection(url);
                remoteService.notifyParticipation(networkName, masterId,
                        participants);
            }
        }
        pendingParticipants.clear();
    }

    /**
     * This method runs the pending commands to SameState.
     *
     * It should be called by the worker thread, but can be called directly
     * for testing purposes to avoid threading in unit tests.
     */
    synchronized void internalRun() {
        handleNewParticipants();
    }

    public synchronized void run() {
        while (!stopped) {
            internalRun();
            try {
                wait(1000);
            } catch (InterruptedException e) {
                // Ignore interrupt in wait loop.
            }
        }
    }

    public synchronized void stopSame() {
        try {
            stopped = true;
            notifyAll();
            this.join();
        } catch (InterruptedException e) {
            logger.warn("Got InterruptedException while waiting for SameState " +
                    "to finish. Ignoring.");
        }
    }
}