From 180be7de07014aa33bc6066f12a0819b731c1c9d Mon Sep 17 00:00:00 2001 From: Dr Scofield Date: Tue, 10 Feb 2009 13:10:57 +0000 Subject: this is step 2 of 2 of the OpenSim.Region.Environment refactor. NOTHING has been deleted or moved off to forge at this point. what has happened is that OpenSim.Region.Environment.Modules has been split in two: - OpenSim.Region.CoreModules: all those modules that are either directly or indirectly referenced from other OpenSim packages, or that provide functionality that the OpenSim developer community considers core functionality: CoreModules/Agent/AssetTransaction CoreModules/Agent/Capabilities CoreModules/Agent/TextureDownload CoreModules/Agent/TextureSender CoreModules/Agent/TextureSender/Tests CoreModules/Agent/Xfer CoreModules/Avatar/AvatarFactory CoreModules/Avatar/Chat/ChatModule CoreModules/Avatar/Combat CoreModules/Avatar/Currency/SampleMoney CoreModules/Avatar/Dialog CoreModules/Avatar/Friends CoreModules/Avatar/Gestures CoreModules/Avatar/Groups CoreModules/Avatar/InstantMessage CoreModules/Avatar/Inventory CoreModules/Avatar/Inventory/Archiver CoreModules/Avatar/Inventory/Transfer CoreModules/Avatar/Lure CoreModules/Avatar/ObjectCaps CoreModules/Avatar/Profiles CoreModules/Communications/Local CoreModules/Communications/REST CoreModules/Framework/EventQueue CoreModules/Framework/InterfaceCommander CoreModules/Hypergrid CoreModules/InterGrid CoreModules/Scripting/DynamicTexture CoreModules/Scripting/EMailModules CoreModules/Scripting/HttpRequest CoreModules/Scripting/LoadImageURL CoreModules/Scripting/VectorRender CoreModules/Scripting/WorldComm CoreModules/Scripting/XMLRPC CoreModules/World/Archiver CoreModules/World/Archiver/Tests CoreModules/World/Estate CoreModules/World/Land CoreModules/World/Permissions CoreModules/World/Serialiser CoreModules/World/Sound CoreModules/World/Sun CoreModules/World/Terrain CoreModules/World/Terrain/DefaultEffects CoreModules/World/Terrain/DefaultEffects/bin CoreModules/World/Terrain/DefaultEffects/bin/Debug CoreModules/World/Terrain/Effects CoreModules/World/Terrain/FileLoaders CoreModules/World/Terrain/FloodBrushes CoreModules/World/Terrain/PaintBrushes CoreModules/World/Terrain/Tests CoreModules/World/Vegetation CoreModules/World/Wind CoreModules/World/WorldMap - OpenSim.Region.OptionalModules: all those modules that are not core modules: OptionalModules/Avatar/Chat/IRC-stuff OptionalModules/Avatar/Concierge OptionalModules/Avatar/Voice/AsterixVoice OptionalModules/Avatar/Voice/SIPVoice OptionalModules/ContentManagementSystem OptionalModules/Grid/Interregion OptionalModules/Python OptionalModules/SvnSerialiser OptionalModules/World/NPC OptionalModules/World/TreePopulator --- .../CoreModules/Scripting/XMLRPC/XMLRPCModule.cs | 726 +++++++++++++++++++++ 1 file changed, 726 insertions(+) create mode 100644 OpenSim/Region/CoreModules/Scripting/XMLRPC/XMLRPCModule.cs (limited to 'OpenSim/Region/CoreModules/Scripting/XMLRPC/XMLRPCModule.cs') diff --git a/OpenSim/Region/CoreModules/Scripting/XMLRPC/XMLRPCModule.cs b/OpenSim/Region/CoreModules/Scripting/XMLRPC/XMLRPCModule.cs new file mode 100644 index 0000000..942c130 --- /dev/null +++ b/OpenSim/Region/CoreModules/Scripting/XMLRPC/XMLRPCModule.cs @@ -0,0 +1,726 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Reflection; +using System.Threading; +using OpenMetaverse; +using log4net; +using Nini.Config; +using Nwc.XmlRpc; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +/***************************************************** + * + * XMLRPCModule + * + * Module for accepting incoming communications from + * external XMLRPC client and calling a remote data + * procedure for a registered data channel/prim. + * + * + * 1. On module load, open a listener port + * 2. Attach an XMLRPC handler + * 3. When a request is received: + * 3.1 Parse into components: channel key, int, string + * 3.2 Look up registered channel listeners + * 3.3 Call the channel (prim) remote data method + * 3.4 Capture the response (llRemoteDataReply) + * 3.5 Return response to client caller + * 3.6 If no response from llRemoteDataReply within + * RemoteReplyScriptTimeout, generate script timeout fault + * + * Prims in script must: + * 1. Open a remote data channel + * 1.1 Generate a channel ID + * 1.2 Register primid,channelid pair with module + * 2. Implement the remote data procedure handler + * + * llOpenRemoteDataChannel + * llRemoteDataReply + * remote_data(integer type, key channel, key messageid, string sender, integer ival, string sval) + * llCloseRemoteDataChannel + * + * **************************************************/ + +namespace OpenSim.Region.CoreModules.Scripting.XMLRPC +{ + public class XMLRPCModule : IRegionModule, IXMLRPC + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private string m_name = "XMLRPCModule"; + + // + private Dictionary m_openChannels; + private Dictionary m_pendingSRDResponses; + private int m_remoteDataPort = 0; + + private Dictionary m_rpcPending; + private Dictionary m_rpcPendingResponses; + private List m_scenes = new List(); + private int RemoteReplyScriptTimeout = 9000; + private int RemoteReplyScriptWait = 300; + private object XMLRPCListLock = new object(); + + #region IRegionModule Members + + public void Initialise(Scene scene, IConfigSource config) + { + // We need to create these early because the scripts might be calling + // But since this gets called for every region, we need to make sure they + // get called only one time (or we lose any open channels) + if (null == m_openChannels) + { + m_openChannels = new Dictionary(); + m_rpcPending = new Dictionary(); + m_rpcPendingResponses = new Dictionary(); + m_pendingSRDResponses = new Dictionary(); + + try + { + m_remoteDataPort = config.Configs["Network"].GetInt("remoteDataPort", m_remoteDataPort); + } + catch (Exception) + { + } + } + + if (!m_scenes.Contains(scene)) + { + m_scenes.Add(scene); + + scene.RegisterModuleInterface(this); + } + } + + public void PostInitialise() + { + if (IsEnabled()) + { + // Start http server + // Attach xmlrpc handlers + m_log.Info("[REMOTE_DATA]: " + + "Starting XMLRPC Server on port " + m_remoteDataPort + " for llRemoteData commands."); + BaseHttpServer httpServer = new BaseHttpServer((uint) m_remoteDataPort); + httpServer.AddXmlRPCHandler("llRemoteData", XmlRpcRemoteData); + httpServer.Start(); + } + } + + public void Close() + { + } + + public string Name + { + get { return m_name; } + } + + public bool IsSharedModule + { + get { return true; } + } + + #endregion + + #region IXMLRPC Members + + public bool IsEnabled() + { + return (m_remoteDataPort > 0); + } + + /********************************************** + * OpenXMLRPCChannel + * + * Generate a UUID channel key and add it and + * the prim id to dictionary + * + * A custom channel key can be proposed. + * Otherwise, passing UUID.Zero will generate + * and return a random channel + * + * First check if there is a channel assigned for + * this itemID. If there is, then someone called + * llOpenRemoteDataChannel twice. Just return the + * original channel. Other option is to delete the + * current channel and assign a new one. + * + * ********************************************/ + + public UUID OpenXMLRPCChannel(uint localID, UUID itemID, UUID channelID) + { + UUID newChannel = UUID.Zero; + + // This should no longer happen, but the check is reasonable anyway + if (null == m_openChannels) + { + m_log.Warn("[RemoteDataReply] Attempt to open channel before initialization is complete"); + return newChannel; + } + + //Is a dupe? + foreach (RPCChannelInfo ci in m_openChannels.Values) + { + if (ci.GetItemID().Equals(itemID)) + { + // return the original channel ID for this item + newChannel = ci.GetChannelID(); + break; + } + } + + if (newChannel == UUID.Zero) + { + newChannel = (channelID == UUID.Zero) ? UUID.Random() : channelID; + RPCChannelInfo rpcChanInfo = new RPCChannelInfo(localID, itemID, newChannel); + lock (XMLRPCListLock) + { + m_openChannels.Add(newChannel, rpcChanInfo); + } + } + + return newChannel; + } + + // Delete channels based on itemID + // for when a script is deleted + public void DeleteChannels(UUID itemID) + { + if (m_openChannels != null) + { + ArrayList tmp = new ArrayList(); + + lock (XMLRPCListLock) + { + foreach (RPCChannelInfo li in m_openChannels.Values) + { + if (li.GetItemID().Equals(itemID)) + { + tmp.Add(itemID); + } + } + + IEnumerator tmpEnumerator = tmp.GetEnumerator(); + while (tmpEnumerator.MoveNext()) + m_openChannels.Remove((UUID) tmpEnumerator.Current); + } + } + } + + /********************************************** + * Remote Data Reply + * + * Response to RPC message + * + *********************************************/ + + public void RemoteDataReply(string channel, string message_id, string sdata, int idata) + { + UUID message_key = new UUID(message_id); + UUID channel_key = new UUID(channel); + + RPCRequestInfo rpcInfo = null; + + if (message_key == UUID.Zero) + { + foreach (RPCRequestInfo oneRpcInfo in m_rpcPendingResponses.Values) + if (oneRpcInfo.GetChannelKey() == channel_key) + rpcInfo = oneRpcInfo; + } + else + { + m_rpcPendingResponses.TryGetValue(message_key, out rpcInfo); + } + + if (rpcInfo != null) + { + rpcInfo.SetStrRetval(sdata); + rpcInfo.SetIntRetval(idata); + rpcInfo.SetProcessed(true); + m_rpcPendingResponses.Remove(message_key); + } + else + { + m_log.Warn("[RemoteDataReply]: Channel or message_id not found"); + } + } + + /********************************************** + * CloseXMLRPCChannel + * + * Remove channel from dictionary + * + *********************************************/ + + public void CloseXMLRPCChannel(UUID channelKey) + { + if (m_openChannels.ContainsKey(channelKey)) + m_openChannels.Remove(channelKey); + } + + + public bool hasRequests() + { + lock (XMLRPCListLock) + { + if (m_rpcPending != null) + return (m_rpcPending.Count > 0); + else + return false; + } + } + + public IXmlRpcRequestInfo GetNextCompletedRequest() + { + if (m_rpcPending != null) + { + lock (XMLRPCListLock) + { + foreach (UUID luid in m_rpcPending.Keys) + { + RPCRequestInfo tmpReq; + + if (m_rpcPending.TryGetValue(luid, out tmpReq)) + { + if (!tmpReq.IsProcessed()) return tmpReq; + } + } + } + } + return null; + } + + public void RemoveCompletedRequest(UUID id) + { + lock (XMLRPCListLock) + { + RPCRequestInfo tmp; + if (m_rpcPending.TryGetValue(id, out tmp)) + { + m_rpcPending.Remove(id); + m_rpcPendingResponses.Add(id, tmp); + } + else + { + Console.WriteLine("UNABLE TO REMOVE COMPLETED REQUEST"); + } + } + } + + public UUID SendRemoteData(uint localID, UUID itemID, string channel, string dest, int idata, string sdata) + { + SendRemoteDataRequest req = new SendRemoteDataRequest( + localID, itemID, channel, dest, idata, sdata + ); + m_pendingSRDResponses.Add(req.GetReqID(), req); + req.Process(); + return req.ReqID; + } + + public IServiceRequest GetNextCompletedSRDRequest() + { + if (m_pendingSRDResponses != null) + { + lock (XMLRPCListLock) + { + foreach (UUID luid in m_pendingSRDResponses.Keys) + { + SendRemoteDataRequest tmpReq; + + if (m_pendingSRDResponses.TryGetValue(luid, out tmpReq)) + { + if (tmpReq.Finished) + return tmpReq; + } + } + } + } + return null; + } + + public void RemoveCompletedSRDRequest(UUID id) + { + lock (XMLRPCListLock) + { + SendRemoteDataRequest tmpReq; + if (m_pendingSRDResponses.TryGetValue(id, out tmpReq)) + { + m_pendingSRDResponses.Remove(id); + } + } + } + + public void CancelSRDRequests(UUID itemID) + { + if (m_pendingSRDResponses != null) + { + lock (XMLRPCListLock) + { + foreach (SendRemoteDataRequest li in m_pendingSRDResponses.Values) + { + if (li.ItemID.Equals(itemID)) + m_pendingSRDResponses.Remove(li.GetReqID()); + } + } + } + } + + #endregion + + public XmlRpcResponse XmlRpcRemoteData(XmlRpcRequest request) + { + XmlRpcResponse response = new XmlRpcResponse(); + + Hashtable requestData = (Hashtable) request.Params[0]; + bool GoodXML = (requestData.Contains("Channel") && requestData.Contains("IntValue") && + requestData.Contains("StringValue")); + + if (GoodXML) + { + UUID channel = new UUID((string) requestData["Channel"]); + RPCChannelInfo rpcChanInfo; + if (m_openChannels.TryGetValue(channel, out rpcChanInfo)) + { + string intVal = Convert.ToInt32(requestData["IntValue"]).ToString(); + string strVal = (string) requestData["StringValue"]; + + RPCRequestInfo rpcInfo; + + lock (XMLRPCListLock) + { + rpcInfo = + new RPCRequestInfo(rpcChanInfo.GetLocalID(), rpcChanInfo.GetItemID(), channel, strVal, + intVal); + m_rpcPending.Add(rpcInfo.GetMessageID(), rpcInfo); + } + + int timeoutCtr = 0; + + while (!rpcInfo.IsProcessed() && (timeoutCtr < RemoteReplyScriptTimeout)) + { + Thread.Sleep(RemoteReplyScriptWait); + timeoutCtr += RemoteReplyScriptWait; + } + if (rpcInfo.IsProcessed()) + { + Hashtable param = new Hashtable(); + param["StringValue"] = rpcInfo.GetStrRetval(); + param["IntValue"] = rpcInfo.GetIntRetval(); + + ArrayList parameters = new ArrayList(); + parameters.Add(param); + + response.Value = parameters; + rpcInfo = null; + } + else + { + response.SetFault(-1, "Script timeout"); + rpcInfo = null; + } + } + else + { + response.SetFault(-1, "Invalid channel"); + } + } + + return response; + } + } + + public class RPCRequestInfo: IXmlRpcRequestInfo + { + private UUID m_ChannelKey; + private string m_IntVal; + private UUID m_ItemID; + private uint m_localID; + private UUID m_MessageID; + private bool m_processed; + private int m_respInt; + private string m_respStr; + private string m_StrVal; + + public RPCRequestInfo(uint localID, UUID itemID, UUID channelKey, string strVal, string intVal) + { + m_localID = localID; + m_StrVal = strVal; + m_IntVal = intVal; + m_ItemID = itemID; + m_ChannelKey = channelKey; + m_MessageID = UUID.Random(); + m_processed = false; + m_respStr = String.Empty; + m_respInt = 0; + } + + public bool IsProcessed() + { + return m_processed; + } + + public UUID GetChannelKey() + { + return m_ChannelKey; + } + + public void SetProcessed(bool processed) + { + m_processed = processed; + } + + public void SetStrRetval(string resp) + { + m_respStr = resp; + } + + public string GetStrRetval() + { + return m_respStr; + } + + public void SetIntRetval(int resp) + { + m_respInt = resp; + } + + public int GetIntRetval() + { + return m_respInt; + } + + public uint GetLocalID() + { + return m_localID; + } + + public UUID GetItemID() + { + return m_ItemID; + } + + public string GetStrVal() + { + return m_StrVal; + } + + public int GetIntValue() + { + return int.Parse(m_IntVal); + } + + public UUID GetMessageID() + { + return m_MessageID; + } + } + + public class RPCChannelInfo + { + private UUID m_ChannelKey; + private UUID m_itemID; + private uint m_localID; + + public RPCChannelInfo(uint localID, UUID itemID, UUID channelID) + { + m_ChannelKey = channelID; + m_localID = localID; + m_itemID = itemID; + } + + public UUID GetItemID() + { + return m_itemID; + } + + public UUID GetChannelID() + { + return m_ChannelKey; + } + + public uint GetLocalID() + { + return m_localID; + } + } + + public class SendRemoteDataRequest: IServiceRequest + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + public string Channel; + public string DestURL; + private bool _finished; + public bool Finished + { + get { return _finished; } + set { _finished = value; } + } + private Thread httpThread; + public int Idata; + private UUID _itemID; + public UUID ItemID + { + get { return _itemID; } + set { _itemID = value; } + } + private uint _localID; + public uint LocalID + { + get { return _localID; } + set { _localID = value; } + } + private UUID _reqID; + public UUID ReqID + { + get { return _reqID; } + set { _reqID = value; } + } + public XmlRpcRequest Request; + public int ResponseIdata; + public string ResponseSdata; + public string Sdata; + + public SendRemoteDataRequest(uint localID, UUID itemID, string channel, string dest, int idata, string sdata) + { + this.Channel = channel; + DestURL = dest; + this.Idata = idata; + this.Sdata = sdata; + ItemID = itemID; + LocalID = localID; + + ReqID = UUID.Random(); + } + + public void Process() + { + httpThread = new Thread(SendRequest); + httpThread.Name = "HttpRequestThread"; + httpThread.Priority = ThreadPriority.BelowNormal; + httpThread.IsBackground = true; + _finished = false; + httpThread.Start(); + ThreadTracker.Add(httpThread); + } + + /* + * TODO: More work on the response codes. Right now + * returning 200 for success or 499 for exception + */ + + public void SendRequest() + { + Hashtable param = new Hashtable(); + + // Check if channel is an UUID + // if not, use as method name + UUID parseUID; + string mName = "llRemoteData"; + if ((Channel != null) && (Channel != "")) + if (!UUID.TryParse(Channel, out parseUID)) + mName = Channel; + else + param["Channel"] = Channel; + + param["StringValue"] = Sdata; + param["IntValue"] = Convert.ToString(Idata); + + ArrayList parameters = new ArrayList(); + parameters.Add(param); + XmlRpcRequest req = new XmlRpcRequest(mName, parameters); + try + { + XmlRpcResponse resp = req.Send(DestURL, 30000); + if (resp != null) + { + Hashtable respParms; + if (resp.Value.GetType().Equals(typeof(System.Collections.Hashtable))) + { + respParms = (Hashtable) resp.Value; + } + else + { + ArrayList respData = (ArrayList) resp.Value; + respParms = (Hashtable) respData[0]; + } + if (respParms != null) + { + if (respParms.Contains("StringValue")) + { + Sdata = (string) respParms["StringValue"]; + } + if (respParms.Contains("IntValue")) + { + Idata = Convert.ToInt32((string) respParms["IntValue"]); + } + if (respParms.Contains("faultString")) + { + Sdata = (string) respParms["faultString"]; + } + if (respParms.Contains("faultCode")) + { + Idata = Convert.ToInt32(respParms["faultCode"]); + } + } + } + } + catch (Exception we) + { + Sdata = we.Message; + m_log.Warn("[SendRemoteDataRequest]: Request failed"); + m_log.Warn(we.StackTrace); + } + + _finished = true; + } + + public void Stop() + { + try + { + httpThread.Abort(); + } + catch (Exception) + { + } + } + + public UUID GetReqID() + { + return ReqID; + } + } +} -- cgit v1.1