1 | import importlib
|
---|
2 | import re
|
---|
3 | from typing import List
|
---|
4 |
|
---|
5 | from tudelft.utilities.listener.Listener import Listener
|
---|
6 | from uri.uri import URI
|
---|
7 |
|
---|
8 | from geniusweb.actions.Action import Action
|
---|
9 | from geniusweb.actions.PartyId import PartyId
|
---|
10 | from geniusweb.inform.Inform import Inform
|
---|
11 | from geniusweb.party.Party import Party
|
---|
12 | from geniusweb.protocol.partyconnection.ProtocolToPartyConn import ProtocolToPartyConn
|
---|
13 | from geniusweb.protocol.partyconnection.ProtocolToPartyConnFactory import ProtocolToPartyConnFactory
|
---|
14 | from geniusweb.references.Reference import Reference
|
---|
15 | from geniusweb.simplerunner.BasicConnection import BasicConnection
|
---|
16 | from geniusweb.utils import val
|
---|
17 |
|
---|
18 |
|
---|
19 | class BasicConnectionWithParty (BasicConnection[Action, Inform], ProtocolToPartyConn):
|
---|
20 |
|
---|
21 | _id: PartyId
|
---|
22 |
|
---|
23 | def __init__(self, reference:Reference ,uri: URI):
|
---|
24 | super().__init__(reference, uri)
|
---|
25 | self._id = PartyId(re.sub("\.", "_", str(uri).replace("pythonpath:", "")))
|
---|
26 |
|
---|
27 | def getParty(self)->PartyId :
|
---|
28 | return self._id
|
---|
29 |
|
---|
30 | def __repr__(self)->str:
|
---|
31 | return "WP" + super().__repr__()
|
---|
32 |
|
---|
33 |
|
---|
34 | class ConnectionPair:
|
---|
35 | '''
|
---|
36 | Contains the connection from protocol to party and from party to protocol.
|
---|
37 | These two are dependent on each other, and must close together.
|
---|
38 | '''
|
---|
39 | serialcounter = 1 # class variable. Keeps track of used valies.
|
---|
40 | _SCHEME = "pythonpath"
|
---|
41 | _PROTOCOLURI = URI("protocol:protocol")
|
---|
42 |
|
---|
43 | _party2protocol: BasicConnection[Inform, Action]
|
---|
44 | _protocol2party: BasicConnectionWithParty
|
---|
45 |
|
---|
46 | def __init__(self, reference:Reference ) :
|
---|
47 | '''
|
---|
48 | @param reference the party class path reference4
|
---|
49 | '''
|
---|
50 | # set up the whole other party including the connection to it.
|
---|
51 | classpath = self._getClassPath(reference.getURI())
|
---|
52 | party = self._instantiate(classpath)
|
---|
53 | this=self
|
---|
54 |
|
---|
55 | # if call to protocol would fail, we should also close the
|
---|
56 | # protocol2party connection. But we assume that will work fine.
|
---|
57 | class myBC(BasicConnection[Inform, Action]):
|
---|
58 | def __init__(self):
|
---|
59 | super().__init__(reference, ConnectionPair._PROTOCOLURI)
|
---|
60 |
|
---|
61 | def close(self):
|
---|
62 | if self.isOpen():
|
---|
63 | super().close()
|
---|
64 | this._protocol2party.close()
|
---|
65 |
|
---|
66 | self._party2protocol = myBC()
|
---|
67 |
|
---|
68 | # if callback from protocol to party fails, then also close the
|
---|
69 | # other direction.
|
---|
70 | class myBCP(BasicConnectionWithParty):
|
---|
71 | def __init__(self):
|
---|
72 | super().__init__(reference, \
|
---|
73 | URI("pythonpath:" + reference.getURI().getPath()\
|
---|
74 | + "." + str(ConnectionPair.serialcounter)))
|
---|
75 | ConnectionPair.serialcounter += 1
|
---|
76 |
|
---|
77 | def close(self):
|
---|
78 | if self.isOpen():
|
---|
79 | super().close()
|
---|
80 | this._party2protocol.close()
|
---|
81 |
|
---|
82 |
|
---|
83 | self._protocol2party = myBCP()
|
---|
84 |
|
---|
85 | class party2protocolListener(Listener[Action]):
|
---|
86 | def notifyChange(self, data: Action):
|
---|
87 | this._protocol2party.notifyListeners(data)
|
---|
88 | self._party2protocol.init(party2protocolListener())
|
---|
89 |
|
---|
90 | class protocol2partyListener(Listener[Inform]):
|
---|
91 | def notifyChange(self, data: Inform):
|
---|
92 | this._party2protocol.notifyListeners(data)
|
---|
93 | self._protocol2party.init(protocol2partyListener())
|
---|
94 |
|
---|
95 | party.connect(self._party2protocol)
|
---|
96 |
|
---|
97 |
|
---|
98 | def getOpenConnections(self)->List[BasicConnection] :
|
---|
99 | '''
|
---|
100 | @return the open connections
|
---|
101 | '''
|
---|
102 | open:List[BasicConnection] = []
|
---|
103 | if self._party2protocol.isOpen():
|
---|
104 | open.append(self._party2protocol)
|
---|
105 | if self._protocol2party.isOpen():
|
---|
106 | open.append(self._protocol2party)
|
---|
107 | return open
|
---|
108 |
|
---|
109 | def getProtocolToPartyConn(self)->BasicConnectionWithParty :
|
---|
110 | return self._protocol2party
|
---|
111 |
|
---|
112 |
|
---|
113 | def _getPartyToProtocolConn(self) ->BasicConnection :
|
---|
114 | '''
|
---|
115 | Internal use for testing
|
---|
116 | '''
|
---|
117 | return self._party2protocol
|
---|
118 |
|
---|
119 | def _instantiate(self, classpath:str)->Party :
|
---|
120 | '''
|
---|
121 | @param classpath to a full.pythonpath.classname as string.
|
---|
122 | The class must be on the python path.
|
---|
123 | the 'full.pythonpath' must be the module "filename",
|
---|
124 | the "classname" is the name of the class inside the module file.
|
---|
125 | @return instance of the given {@link Party} on the classpath
|
---|
126 | @throws ValueError if the Party can not be instantiated, eg
|
---|
127 | because of a ClassCastException,
|
---|
128 | ClassNotFoundException,
|
---|
129 | IllegalAccessException etc. Such an
|
---|
130 | exception is considered a bug by the
|
---|
131 | programmer, because this is a
|
---|
132 | stand-alone runner.
|
---|
133 | '''
|
---|
134 | print("Connecting with party '" + classpath + "'")
|
---|
135 | try:
|
---|
136 |
|
---|
137 | [path, cla] = classpath.rsplit(".",1)
|
---|
138 | mod=importlib.import_module(path)
|
---|
139 | partyclass = getattr(mod, cla)
|
---|
140 | return partyclass()
|
---|
141 | except Exception as e:
|
---|
142 | raise ValueError(
|
---|
143 | "Failed to create connection to party " + classpath,e)
|
---|
144 | importlib
|
---|
145 |
|
---|
146 | def _getClassPath(self, uri:URI) -> str:
|
---|
147 | assert isinstance(uri, URI)
|
---|
148 | if self._SCHEME != uri.getScheme():
|
---|
149 | raise ValueError("Required the " + self._SCHEME
|
---|
150 | + " protocol but found " + uri.getScheme())
|
---|
151 | path = uri.getPath()
|
---|
152 | if not path:
|
---|
153 | raise ValueError(
|
---|
154 | "Expected pythonpath not present in " + str(uri))
|
---|
155 | return path
|
---|
156 |
|
---|
157 |
|
---|
158 |
|
---|
159 | class ClassPathConnectionFactory (ProtocolToPartyConnFactory):
|
---|
160 | '''
|
---|
161 | A connectionfactory that only accepts URLs of the form
|
---|
162 | <code>pythonpath:org/my/package/class</code>
|
---|
163 | '''
|
---|
164 | _allConnections: List[ConnectionPair] = []
|
---|
165 |
|
---|
166 | def connect(self, reference:Reference ) ->ProtocolToPartyConn :
|
---|
167 | '''
|
---|
168 | @param reference the party class path reference4
|
---|
169 | '''
|
---|
170 | connpair = ConnectionPair(reference)
|
---|
171 | self._allConnections.append(connpair)
|
---|
172 | return connpair.getProtocolToPartyConn()
|
---|
173 |
|
---|
174 | def connectAll(self, references:List[Reference] ) ->List[ProtocolToPartyConn] :
|
---|
175 | connections:List[ProtocolToPartyConn] = []
|
---|
176 | for partyref in references:
|
---|
177 | connections.append(self.connect(partyref))
|
---|
178 | return connections
|
---|
179 |
|
---|
180 | def getOpenConnections(self)->List[BasicConnection] :
|
---|
181 | '''
|
---|
182 | @return list of connections that are still open.
|
---|
183 | '''
|
---|
184 | openconns:List[BasicConnection] = []
|
---|
185 | for connpair in self._allConnections:
|
---|
186 | openconns.extend(connpair.getOpenConnections())
|
---|
187 | return openconns
|
---|