[72] | 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
|
---|