diff options
First step in separating out the Userserver console command handling to a "module".
Added OpenSim.Grid.UserServer.Modules project/dll which now contains the components of the userserver. With the OpenSim.Grid.UserServer being the setup and initiate exe.
Diffstat (limited to 'OpenSim/Grid/UserServer.Modules/MessageServersConnector.cs')
-rw-r--r-- | OpenSim/Grid/UserServer.Modules/MessageServersConnector.cs | 509 |
1 files changed, 509 insertions, 0 deletions
diff --git a/OpenSim/Grid/UserServer.Modules/MessageServersConnector.cs b/OpenSim/Grid/UserServer.Modules/MessageServersConnector.cs new file mode 100644 index 0000000..b98c614 --- /dev/null +++ b/OpenSim/Grid/UserServer.Modules/MessageServersConnector.cs | |||
@@ -0,0 +1,509 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSim Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System.Collections; | ||
29 | using System.Collections.Generic; | ||
30 | using System.Net; | ||
31 | using System.Reflection; | ||
32 | using System.Threading; | ||
33 | using log4net; | ||
34 | using Nwc.XmlRpc; | ||
35 | using OpenMetaverse; | ||
36 | using OpenSim.Framework; | ||
37 | using OpenSim.Framework.Servers; | ||
38 | |||
39 | namespace OpenSim.Grid.UserServer.Modules | ||
40 | { | ||
41 | public enum NotificationRequest : int | ||
42 | { | ||
43 | Login = 0, | ||
44 | Logout = 1, | ||
45 | Shutdown = 2 | ||
46 | } | ||
47 | |||
48 | public struct PresenceNotification | ||
49 | { | ||
50 | public NotificationRequest request; | ||
51 | public UUID agentID; | ||
52 | public UUID sessionID; | ||
53 | public UUID RegionID; | ||
54 | public ulong regionhandle; | ||
55 | public float positionX; | ||
56 | public float positionY; | ||
57 | public float positionZ; | ||
58 | public string firstname; | ||
59 | public string lastname; | ||
60 | } | ||
61 | |||
62 | public delegate void AgentLocationDelegate(UUID agentID, UUID regionID, ulong regionHandle); | ||
63 | public delegate void AgentLeavingDelegate(UUID agentID, UUID regionID, ulong regionHandle); | ||
64 | public delegate void RegionStartupDelegate(UUID regionID); | ||
65 | public delegate void RegionShutdownDelegate(UUID regionID); | ||
66 | |||
67 | |||
68 | public class MessageServersConnector | ||
69 | { | ||
70 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
71 | |||
72 | public Dictionary<string, MessageServerInfo> MessageServers; | ||
73 | |||
74 | private BaseHttpServer m_httpServer; | ||
75 | |||
76 | private BlockingQueue<PresenceNotification> m_NotifyQueue = | ||
77 | new BlockingQueue<PresenceNotification>(); | ||
78 | |||
79 | Thread m_NotifyThread; | ||
80 | |||
81 | public event AgentLocationDelegate OnAgentLocation; | ||
82 | public event AgentLeavingDelegate OnAgentLeaving; | ||
83 | public event RegionStartupDelegate OnRegionStartup; | ||
84 | public event RegionShutdownDelegate OnRegionShutdown; | ||
85 | |||
86 | public MessageServersConnector() | ||
87 | { | ||
88 | MessageServers = new Dictionary<string, MessageServerInfo>(); | ||
89 | m_NotifyThread = new Thread(new ThreadStart(NotifyQueueRunner)); | ||
90 | m_NotifyThread.Start(); | ||
91 | } | ||
92 | |||
93 | public void Initialise() | ||
94 | { | ||
95 | |||
96 | } | ||
97 | |||
98 | public void PostInitialise() | ||
99 | { | ||
100 | |||
101 | } | ||
102 | |||
103 | public void RegisterHandlers(BaseHttpServer httpServer) | ||
104 | { | ||
105 | m_httpServer = httpServer; | ||
106 | |||
107 | m_httpServer.AddXmlRPCHandler("region_startup", RegionStartup); | ||
108 | m_httpServer.AddXmlRPCHandler("region_shutdown", RegionShutdown); | ||
109 | m_httpServer.AddXmlRPCHandler("agent_location", AgentLocation); | ||
110 | m_httpServer.AddXmlRPCHandler("agent_leaving", AgentLeaving); | ||
111 | // Message Server ---> User Server | ||
112 | m_httpServer.AddXmlRPCHandler("register_messageserver", XmlRPCRegisterMessageServer); | ||
113 | m_httpServer.AddXmlRPCHandler("agent_change_region", XmlRPCUserMovedtoRegion); | ||
114 | m_httpServer.AddXmlRPCHandler("deregister_messageserver", XmlRPCDeRegisterMessageServer); | ||
115 | } | ||
116 | |||
117 | public void RegisterMessageServer(string URI, MessageServerInfo serverData) | ||
118 | { | ||
119 | lock (MessageServers) | ||
120 | { | ||
121 | if (!MessageServers.ContainsKey(URI)) | ||
122 | MessageServers.Add(URI, serverData); | ||
123 | } | ||
124 | } | ||
125 | |||
126 | public void DeRegisterMessageServer(string URI) | ||
127 | { | ||
128 | lock (MessageServers) | ||
129 | { | ||
130 | if (MessageServers.ContainsKey(URI)) | ||
131 | MessageServers.Remove(URI); | ||
132 | } | ||
133 | } | ||
134 | |||
135 | public void AddResponsibleRegion(string URI, ulong regionhandle) | ||
136 | { | ||
137 | if (!MessageServers.ContainsKey(URI)) | ||
138 | { | ||
139 | m_log.Warn("[MSGSERVER]: Got addResponsibleRegion Request for a MessageServer that isn't registered"); | ||
140 | } | ||
141 | else | ||
142 | { | ||
143 | MessageServerInfo msginfo = MessageServers["URI"]; | ||
144 | msginfo.responsibleForRegions.Add(regionhandle); | ||
145 | MessageServers["URI"] = msginfo; | ||
146 | } | ||
147 | } | ||
148 | public void RemoveResponsibleRegion(string URI, ulong regionhandle) | ||
149 | { | ||
150 | if (!MessageServers.ContainsKey(URI)) | ||
151 | { | ||
152 | m_log.Warn("[MSGSERVER]: Got RemoveResponsibleRegion Request for a MessageServer that isn't registered"); | ||
153 | } | ||
154 | else | ||
155 | { | ||
156 | MessageServerInfo msginfo = MessageServers["URI"]; | ||
157 | if (msginfo.responsibleForRegions.Contains(regionhandle)) | ||
158 | { | ||
159 | msginfo.responsibleForRegions.Remove(regionhandle); | ||
160 | MessageServers["URI"] = msginfo; | ||
161 | } | ||
162 | } | ||
163 | |||
164 | } | ||
165 | public XmlRpcResponse XmlRPCRegisterMessageServer(XmlRpcRequest request) | ||
166 | { | ||
167 | XmlRpcResponse response = new XmlRpcResponse(); | ||
168 | Hashtable requestData = (Hashtable)request.Params[0]; | ||
169 | Hashtable responseData = new Hashtable(); | ||
170 | |||
171 | if (requestData.Contains("uri")) | ||
172 | { | ||
173 | string URI = (string)requestData["uri"]; | ||
174 | string sendkey=(string)requestData["sendkey"]; | ||
175 | string recvkey=(string)requestData["recvkey"]; | ||
176 | MessageServerInfo m = new MessageServerInfo(); | ||
177 | m.URI = URI; | ||
178 | m.sendkey = sendkey; | ||
179 | m.recvkey = recvkey; | ||
180 | RegisterMessageServer(URI, m); | ||
181 | responseData["responsestring"] = "TRUE"; | ||
182 | response.Value = responseData; | ||
183 | } | ||
184 | return response; | ||
185 | } | ||
186 | public XmlRpcResponse XmlRPCDeRegisterMessageServer(XmlRpcRequest request) | ||
187 | { | ||
188 | XmlRpcResponse response = new XmlRpcResponse(); | ||
189 | Hashtable requestData = (Hashtable)request.Params[0]; | ||
190 | Hashtable responseData = new Hashtable(); | ||
191 | |||
192 | if (requestData.Contains("uri")) | ||
193 | { | ||
194 | string URI = (string)requestData["uri"]; | ||
195 | |||
196 | DeRegisterMessageServer(URI); | ||
197 | responseData["responsestring"] = "TRUE"; | ||
198 | response.Value = responseData; | ||
199 | } | ||
200 | return response; | ||
201 | } | ||
202 | public XmlRpcResponse XmlRPCUserMovedtoRegion(XmlRpcRequest request) | ||
203 | { | ||
204 | XmlRpcResponse response = new XmlRpcResponse(); | ||
205 | Hashtable requestData = (Hashtable)request.Params[0]; | ||
206 | Hashtable responseData = new Hashtable(); | ||
207 | |||
208 | if (requestData.Contains("fromuri")) | ||
209 | { | ||
210 | // string sURI = (string)requestData["fromuri"]; | ||
211 | // string sagentID = (string)requestData["agentid"]; | ||
212 | // string ssessionID = (string)requestData["sessionid"]; | ||
213 | // string scurrentRegionID = (string)requestData["regionid"]; | ||
214 | // string sregionhandle = (string)requestData["regionhandle"]; | ||
215 | // string scurrentpos = (string)requestData["currentpos"]; | ||
216 | //Vector3.TryParse((string)reader["currentPos"], out retval.currentPos); | ||
217 | // TODO: Okay now raise event so the user server can pass this data to the Usermanager | ||
218 | |||
219 | responseData["responsestring"] = "TRUE"; | ||
220 | response.Value = responseData; | ||
221 | } | ||
222 | return response; | ||
223 | } | ||
224 | |||
225 | public void TellMessageServersAboutUser(UUID agentID, UUID sessionID, UUID RegionID, | ||
226 | ulong regionhandle, float positionX, float positionY, | ||
227 | float positionZ, string firstname, string lastname) | ||
228 | { | ||
229 | PresenceNotification notification = new PresenceNotification(); | ||
230 | |||
231 | notification.request = NotificationRequest.Login; | ||
232 | notification.agentID = agentID; | ||
233 | notification.sessionID = sessionID; | ||
234 | notification.RegionID = RegionID; | ||
235 | notification.regionhandle = regionhandle; | ||
236 | notification.positionX = positionX; | ||
237 | notification.positionY = positionY; | ||
238 | notification.positionZ = positionZ; | ||
239 | notification.firstname = firstname; | ||
240 | notification.lastname = lastname; | ||
241 | |||
242 | m_NotifyQueue.Enqueue(notification); | ||
243 | } | ||
244 | |||
245 | private void TellMessageServersAboutUserInternal(UUID agentID, UUID sessionID, UUID RegionID, | ||
246 | ulong regionhandle, float positionX, float positionY, | ||
247 | float positionZ, string firstname, string lastname) | ||
248 | { | ||
249 | // Loop over registered Message Servers (AND THERE WILL BE MORE THEN ONE :D) | ||
250 | lock (MessageServers) | ||
251 | { | ||
252 | if (MessageServers.Count > 0) | ||
253 | { | ||
254 | m_log.Info("[MSGCONNECTOR]: Sending login notice to registered message servers"); | ||
255 | } | ||
256 | // else | ||
257 | // { | ||
258 | // m_log.Debug("[MSGCONNECTOR]: No Message Servers registered, ignoring"); | ||
259 | // } | ||
260 | foreach (MessageServerInfo serv in MessageServers.Values) | ||
261 | { | ||
262 | NotifyMessageServerAboutUser(serv, agentID, sessionID, RegionID, | ||
263 | regionhandle, positionX, positionY, positionZ, | ||
264 | firstname, lastname); | ||
265 | } | ||
266 | } | ||
267 | } | ||
268 | |||
269 | private void TellMessageServersAboutUserLogoffInternal(UUID agentID) | ||
270 | { | ||
271 | lock (MessageServers) | ||
272 | { | ||
273 | if (MessageServers.Count > 0) | ||
274 | { | ||
275 | m_log.Info("[MSGCONNECTOR]: Sending logoff notice to registered message servers"); | ||
276 | } | ||
277 | else | ||
278 | { | ||
279 | // m_log.Debug("[MSGCONNECTOR]: No Message Servers registered, ignoring"); | ||
280 | } | ||
281 | foreach (MessageServerInfo serv in MessageServers.Values) | ||
282 | { | ||
283 | NotifyMessageServerAboutUserLogoff(serv,agentID); | ||
284 | } | ||
285 | } | ||
286 | } | ||
287 | |||
288 | private void TellMessageServersAboutRegionShutdownInternal(UUID regionID) | ||
289 | { | ||
290 | lock (MessageServers) | ||
291 | { | ||
292 | if (MessageServers.Count > 0) | ||
293 | { | ||
294 | m_log.Info("[MSGCONNECTOR]: Sending region down notice to registered message servers"); | ||
295 | } | ||
296 | else | ||
297 | { | ||
298 | // m_log.Debug("[MSGCONNECTOR]: No Message Servers registered, ignoring"); | ||
299 | } | ||
300 | foreach (MessageServerInfo serv in MessageServers.Values) | ||
301 | { | ||
302 | NotifyMessageServerAboutRegionShutdown(serv,regionID); | ||
303 | } | ||
304 | } | ||
305 | } | ||
306 | |||
307 | public void TellMessageServersAboutUserLogoff(UUID agentID) | ||
308 | { | ||
309 | PresenceNotification notification = new PresenceNotification(); | ||
310 | |||
311 | notification.request = NotificationRequest.Logout; | ||
312 | notification.agentID = agentID; | ||
313 | |||
314 | m_NotifyQueue.Enqueue(notification); | ||
315 | } | ||
316 | |||
317 | public void TellMessageServersAboutRegionShutdown(UUID regionID) | ||
318 | { | ||
319 | PresenceNotification notification = new PresenceNotification(); | ||
320 | |||
321 | notification.request = NotificationRequest.Shutdown; | ||
322 | notification.RegionID = regionID; | ||
323 | |||
324 | m_NotifyQueue.Enqueue(notification); | ||
325 | } | ||
326 | |||
327 | private void NotifyMessageServerAboutUserLogoff(MessageServerInfo serv, UUID agentID) | ||
328 | { | ||
329 | Hashtable reqparams = new Hashtable(); | ||
330 | reqparams["sendkey"] = serv.sendkey; | ||
331 | reqparams["agentid"] = agentID.ToString(); | ||
332 | ArrayList SendParams = new ArrayList(); | ||
333 | SendParams.Add(reqparams); | ||
334 | |||
335 | XmlRpcRequest GridReq = new XmlRpcRequest("logout_of_simulator", SendParams); | ||
336 | try | ||
337 | { | ||
338 | GridReq.Send(serv.URI, 6000); | ||
339 | } | ||
340 | catch (WebException) | ||
341 | { | ||
342 | m_log.Warn("[MSGCONNECTOR]: Unable to notify Message Server about log out. Other users might still think this user is online"); | ||
343 | } | ||
344 | m_log.Info("[LOGOUT]: Notified : " + serv.URI + " about user logout"); | ||
345 | } | ||
346 | |||
347 | private void NotifyMessageServerAboutRegionShutdown(MessageServerInfo serv, UUID regionID) | ||
348 | { | ||
349 | Hashtable reqparams = new Hashtable(); | ||
350 | reqparams["sendkey"] = serv.sendkey; | ||
351 | reqparams["regionid"] = regionID.ToString(); | ||
352 | ArrayList SendParams = new ArrayList(); | ||
353 | SendParams.Add(reqparams); | ||
354 | |||
355 | XmlRpcRequest GridReq = new XmlRpcRequest("process_region_shutdown", SendParams); | ||
356 | try | ||
357 | { | ||
358 | GridReq.Send(serv.URI, 6000); | ||
359 | } | ||
360 | catch (WebException) | ||
361 | { | ||
362 | m_log.Warn("[MSGCONNECTOR]: Unable to notify Message Server about region shutdown."); | ||
363 | } | ||
364 | m_log.Info("[REGION UPDOWN]: Notified : " + serv.URI + " about region state change"); | ||
365 | } | ||
366 | |||
367 | private void NotifyMessageServerAboutUser(MessageServerInfo serv, | ||
368 | UUID agentID, UUID sessionID, UUID RegionID, | ||
369 | ulong regionhandle, float positionX, float positionY, float positionZ, | ||
370 | string firstname, string lastname) | ||
371 | { | ||
372 | Hashtable reqparams = new Hashtable(); | ||
373 | reqparams["sendkey"] = serv.sendkey; | ||
374 | reqparams["agentid"] = agentID.ToString(); | ||
375 | reqparams["sessionid"] = sessionID.ToString(); | ||
376 | reqparams["regionid"] = RegionID.ToString(); | ||
377 | reqparams["regionhandle"] = regionhandle.ToString(); | ||
378 | reqparams["positionx"] = positionX.ToString(); | ||
379 | reqparams["positiony"] = positionY.ToString(); | ||
380 | reqparams["positionz"] = positionZ.ToString(); | ||
381 | reqparams["firstname"] = firstname; | ||
382 | reqparams["lastname"] = lastname; | ||
383 | |||
384 | //reqparams["position"] = Position.ToString(); | ||
385 | |||
386 | ArrayList SendParams = new ArrayList(); | ||
387 | SendParams.Add(reqparams); | ||
388 | |||
389 | XmlRpcRequest GridReq = new XmlRpcRequest("login_to_simulator", SendParams); | ||
390 | try | ||
391 | { | ||
392 | GridReq.Send(serv.URI, 6000); | ||
393 | m_log.Info("[LOGIN]: Notified : " + serv.URI + " about user login"); | ||
394 | } | ||
395 | catch (WebException) | ||
396 | { | ||
397 | m_log.Warn("[MSGCONNECTOR]: Unable to notify Message Server about login. Presence might be borked for this user"); | ||
398 | } | ||
399 | |||
400 | } | ||
401 | |||
402 | private void NotifyQueueRunner() | ||
403 | { | ||
404 | while (true) | ||
405 | { | ||
406 | PresenceNotification presence = m_NotifyQueue.Dequeue(); | ||
407 | |||
408 | if (presence.request == NotificationRequest.Shutdown) | ||
409 | { | ||
410 | TellMessageServersAboutRegionShutdownInternal(presence.RegionID); | ||
411 | } | ||
412 | |||
413 | if (presence.request == NotificationRequest.Login) | ||
414 | { | ||
415 | TellMessageServersAboutUserInternal(presence.agentID, | ||
416 | presence.sessionID, presence.RegionID, | ||
417 | presence.regionhandle, presence.positionX, | ||
418 | presence.positionY, presence.positionZ, | ||
419 | presence.firstname, presence.lastname); | ||
420 | } | ||
421 | |||
422 | if (presence.request == NotificationRequest.Logout) | ||
423 | { | ||
424 | TellMessageServersAboutUserLogoffInternal(presence.agentID); | ||
425 | } | ||
426 | } | ||
427 | } | ||
428 | |||
429 | public XmlRpcResponse RegionStartup(XmlRpcRequest request) | ||
430 | { | ||
431 | Hashtable requestData = (Hashtable)request.Params[0]; | ||
432 | Hashtable result = new Hashtable(); | ||
433 | |||
434 | UUID regionID; | ||
435 | if (UUID.TryParse((string)requestData["RegionUUID"], out regionID)) | ||
436 | { | ||
437 | if (OnRegionStartup != null) | ||
438 | OnRegionStartup(regionID); | ||
439 | |||
440 | result["responsestring"] = "TRUE"; | ||
441 | } | ||
442 | |||
443 | XmlRpcResponse response = new XmlRpcResponse(); | ||
444 | response.Value = result; | ||
445 | return response; | ||
446 | } | ||
447 | |||
448 | public XmlRpcResponse RegionShutdown(XmlRpcRequest request) | ||
449 | { | ||
450 | Hashtable requestData = (Hashtable)request.Params[0]; | ||
451 | Hashtable result = new Hashtable(); | ||
452 | |||
453 | UUID regionID; | ||
454 | if (UUID.TryParse((string)requestData["RegionUUID"], out regionID)) | ||
455 | { | ||
456 | if (OnRegionShutdown != null) | ||
457 | OnRegionShutdown(regionID); | ||
458 | |||
459 | result["responsestring"] = "TRUE"; | ||
460 | } | ||
461 | |||
462 | XmlRpcResponse response = new XmlRpcResponse(); | ||
463 | response.Value = result; | ||
464 | return response; | ||
465 | } | ||
466 | |||
467 | public XmlRpcResponse AgentLocation(XmlRpcRequest request) | ||
468 | { | ||
469 | Hashtable requestData = (Hashtable)request.Params[0]; | ||
470 | Hashtable result = new Hashtable(); | ||
471 | |||
472 | UUID agentID; | ||
473 | UUID regionID; | ||
474 | ulong regionHandle; | ||
475 | if (UUID.TryParse((string)requestData["AgentID"], out agentID) && UUID.TryParse((string)requestData["RegionUUID"], out regionID) && ulong.TryParse((string)requestData["RegionHandle"], out regionHandle)) | ||
476 | { | ||
477 | if (OnAgentLocation != null) | ||
478 | OnAgentLocation(agentID, regionID, regionHandle); | ||
479 | |||
480 | result["responsestring"] = "TRUE"; | ||
481 | } | ||
482 | |||
483 | XmlRpcResponse response = new XmlRpcResponse(); | ||
484 | response.Value = result; | ||
485 | return response; | ||
486 | } | ||
487 | |||
488 | public XmlRpcResponse AgentLeaving(XmlRpcRequest request) | ||
489 | { | ||
490 | Hashtable requestData = (Hashtable)request.Params[0]; | ||
491 | Hashtable result = new Hashtable(); | ||
492 | |||
493 | UUID agentID; | ||
494 | UUID regionID; | ||
495 | ulong regionHandle; | ||
496 | if (UUID.TryParse((string)requestData["AgentID"], out agentID) && UUID.TryParse((string)requestData["RegionUUID"], out regionID) && ulong.TryParse((string)requestData["RegionHandle"], out regionHandle)) | ||
497 | { | ||
498 | if (OnAgentLeaving != null) | ||
499 | OnAgentLeaving(agentID, regionID, regionHandle); | ||
500 | |||
501 | result["responsestring"] = "TRUE"; | ||
502 | } | ||
503 | |||
504 | XmlRpcResponse response = new XmlRpcResponse(); | ||
505 | response.Value = result; | ||
506 | return response; | ||
507 | } | ||
508 | } | ||
509 | } | ||