aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Grid/GridServer/GridRestModule.cs
diff options
context:
space:
mode:
authorJeff Ames2009-02-22 01:18:49 +0000
committerJeff Ames2009-02-22 01:18:49 +0000
commit551bebdc84515d77bc3d7a564100efd0525a1949 (patch)
tree1e3300c3a60d51a5607b69ee357f1d08936014f3 /OpenSim/Grid/GridServer/GridRestModule.cs
parentApplied patch from mantis #3217, which allows Dynamic Images of type RGB (so ... (diff)
downloadopensim-SC-551bebdc84515d77bc3d7a564100efd0525a1949.zip
opensim-SC-551bebdc84515d77bc3d7a564100efd0525a1949.tar.gz
opensim-SC-551bebdc84515d77bc3d7a564100efd0525a1949.tar.bz2
opensim-SC-551bebdc84515d77bc3d7a564100efd0525a1949.tar.xz
Update svn properties, add copyright headers, minor formatting cleanup.
Diffstat (limited to '')
-rw-r--r--OpenSim/Grid/GridServer/GridRestModule.cs562
1 files changed, 281 insertions, 281 deletions
diff --git a/OpenSim/Grid/GridServer/GridRestModule.cs b/OpenSim/Grid/GridServer/GridRestModule.cs
index 5894c4b..e0c1288 100644
--- a/OpenSim/Grid/GridServer/GridRestModule.cs
+++ b/OpenSim/Grid/GridServer/GridRestModule.cs
@@ -1,281 +1,281 @@
1/* 1/*
2 * Copyright (c) Contributors, http://opensimulator.org/ 2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders. 3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 * 4 *
5 * Redistribution and use in source and binary forms, with or without 5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met: 6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright 7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer. 8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright 9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the 10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution. 11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSim Project nor the 12 * * Neither the name of the OpenSim Project nor the
13 * names of its contributors may be used to endorse or promote products 13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission. 14 * derived from this software without specific prior written permission.
15 * 15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY 16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY 19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 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 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 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 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. 25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */ 26 */
27 27
28using System; 28using System;
29using System.Collections; 29using System.Collections;
30using System.Collections.Generic; 30using System.Collections.Generic;
31using System.IO; 31using System.IO;
32using System.Reflection; 32using System.Reflection;
33using System.Xml; 33using System.Xml;
34using log4net; 34using log4net;
35using OpenMetaverse; 35using OpenMetaverse;
36using OpenSim.Data; 36using OpenSim.Data;
37using OpenSim.Framework; 37using OpenSim.Framework;
38using OpenSim.Framework.Communications; 38using OpenSim.Framework.Communications;
39using OpenSim.Framework.Servers; 39using OpenSim.Framework.Servers;
40 40
41namespace OpenSim.Grid.GridServer 41namespace OpenSim.Grid.GridServer
42{ 42{
43 public class GridRestModule 43 public class GridRestModule
44 { 44 {
45 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 45 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
46 46
47 private GridDBService m_gridDBService; 47 private GridDBService m_gridDBService;
48 private IUGAIMCore m_gridCore; 48 private IUGAIMCore m_gridCore;
49 49
50 protected GridConfig m_config; 50 protected GridConfig m_config;
51 51
52 /// <value> 52 /// <value>
53 /// Used to notify old regions as to which OpenSim version to upgrade to 53 /// Used to notify old regions as to which OpenSim version to upgrade to
54 /// </value> 54 /// </value>
55 private string m_opensimVersion; 55 private string m_opensimVersion;
56 56
57 protected BaseHttpServer m_httpServer; 57 protected BaseHttpServer m_httpServer;
58 58
59 /// <summary> 59 /// <summary>
60 /// Constructor 60 /// Constructor
61 /// </summary> 61 /// </summary>
62 /// <param name="opensimVersion"> 62 /// <param name="opensimVersion">
63 /// Used to notify old regions as to which OpenSim version to upgrade to 63 /// Used to notify old regions as to which OpenSim version to upgrade to
64 /// </param> 64 /// </param>
65 public GridRestModule() 65 public GridRestModule()
66 { 66 {
67 } 67 }
68 68
69 public void Initialise(string opensimVersion, GridDBService gridDBService, IUGAIMCore gridCore, GridConfig config) 69 public void Initialise(string opensimVersion, GridDBService gridDBService, IUGAIMCore gridCore, GridConfig config)
70 { 70 {
71 m_opensimVersion = opensimVersion; 71 m_opensimVersion = opensimVersion;
72 m_gridDBService = gridDBService; 72 m_gridDBService = gridDBService;
73 m_gridCore = gridCore; 73 m_gridCore = gridCore;
74 m_config = config; 74 m_config = config;
75 RegisterHandlers(); 75 RegisterHandlers();
76 } 76 }
77 77
78 public void PostInitialise() 78 public void PostInitialise()
79 { 79 {
80 80
81 } 81 }
82 82
83 public void RegisterHandlers() 83 public void RegisterHandlers()
84 { 84 {
85 //have these in separate method as some servers restart the http server and reregister all the handlers. 85 //have these in separate method as some servers restart the http server and reregister all the handlers.
86 m_httpServer = m_gridCore.GetHttpServer(); 86 m_httpServer = m_gridCore.GetHttpServer();
87 87
88 m_httpServer.AddStreamHandler(new RestStreamHandler("GET", "/sims/", RestGetSimMethod)); 88 m_httpServer.AddStreamHandler(new RestStreamHandler("GET", "/sims/", RestGetSimMethod));
89 m_httpServer.AddStreamHandler(new RestStreamHandler("POST", "/sims/", RestSetSimMethod)); 89 m_httpServer.AddStreamHandler(new RestStreamHandler("POST", "/sims/", RestSetSimMethod));
90 90
91 m_httpServer.AddStreamHandler(new RestStreamHandler("GET", "/regions/", RestGetRegionMethod)); 91 m_httpServer.AddStreamHandler(new RestStreamHandler("GET", "/regions/", RestGetRegionMethod));
92 m_httpServer.AddStreamHandler(new RestStreamHandler("POST", "/regions/", RestSetRegionMethod)); 92 m_httpServer.AddStreamHandler(new RestStreamHandler("POST", "/regions/", RestSetRegionMethod));
93 } 93 }
94 94
95 /// <summary> 95 /// <summary>
96 /// Performs a REST Get Operation 96 /// Performs a REST Get Operation
97 /// </summary> 97 /// </summary>
98 /// <param name="request"></param> 98 /// <param name="request"></param>
99 /// <param name="path"></param> 99 /// <param name="path"></param>
100 /// <param name="param"></param> 100 /// <param name="param"></param>
101 /// <param name="httpRequest">HTTP request header object</param> 101 /// <param name="httpRequest">HTTP request header object</param>
102 /// <param name="httpResponse">HTTP response header object</param> 102 /// <param name="httpResponse">HTTP response header object</param>
103 /// <returns></returns> 103 /// <returns></returns>
104 public string RestGetRegionMethod(string request, string path, string param, 104 public string RestGetRegionMethod(string request, string path, string param,
105 OSHttpRequest httpRequest, OSHttpResponse httpResponse) 105 OSHttpRequest httpRequest, OSHttpResponse httpResponse)
106 { 106 {
107 return RestGetSimMethod(String.Empty, "/sims/", param, httpRequest, httpResponse); 107 return RestGetSimMethod(String.Empty, "/sims/", param, httpRequest, httpResponse);
108 } 108 }
109 109
110 /// <summary> 110 /// <summary>
111 /// Performs a REST Set Operation 111 /// Performs a REST Set Operation
112 /// </summary> 112 /// </summary>
113 /// <param name="request"></param> 113 /// <param name="request"></param>
114 /// <param name="path"></param> 114 /// <param name="path"></param>
115 /// <param name="param"></param> 115 /// <param name="param"></param>
116 /// <param name="httpRequest">HTTP request header object</param> 116 /// <param name="httpRequest">HTTP request header object</param>
117 /// <param name="httpResponse">HTTP response header object</param> 117 /// <param name="httpResponse">HTTP response header object</param>
118 /// <returns></returns> 118 /// <returns></returns>
119 public string RestSetRegionMethod(string request, string path, string param, 119 public string RestSetRegionMethod(string request, string path, string param,
120 OSHttpRequest httpRequest, OSHttpResponse httpResponse) 120 OSHttpRequest httpRequest, OSHttpResponse httpResponse)
121 { 121 {
122 return RestSetSimMethod(String.Empty, "/sims/", param, httpRequest, httpResponse); 122 return RestSetSimMethod(String.Empty, "/sims/", param, httpRequest, httpResponse);
123 } 123 }
124 124
125 /// <summary> 125 /// <summary>
126 /// Returns information about a sim via a REST Request 126 /// Returns information about a sim via a REST Request
127 /// </summary> 127 /// </summary>
128 /// <param name="request"></param> 128 /// <param name="request"></param>
129 /// <param name="path"></param> 129 /// <param name="path"></param>
130 /// <param name="param">A string representing the sim's UUID</param> 130 /// <param name="param">A string representing the sim's UUID</param>
131 /// <param name="httpRequest">HTTP request header object</param> 131 /// <param name="httpRequest">HTTP request header object</param>
132 /// <param name="httpResponse">HTTP response header object</param> 132 /// <param name="httpResponse">HTTP response header object</param>
133 /// <returns>Information about the sim in XML</returns> 133 /// <returns>Information about the sim in XML</returns>
134 public string RestGetSimMethod(string request, string path, string param, 134 public string RestGetSimMethod(string request, string path, string param,
135 OSHttpRequest httpRequest, OSHttpResponse httpResponse) 135 OSHttpRequest httpRequest, OSHttpResponse httpResponse)
136 { 136 {
137 string respstring = String.Empty; 137 string respstring = String.Empty;
138 138
139 RegionProfileData TheSim; 139 RegionProfileData TheSim;
140 140
141 UUID UUID; 141 UUID UUID;
142 if (UUID.TryParse(param, out UUID)) 142 if (UUID.TryParse(param, out UUID))
143 { 143 {
144 TheSim = m_gridDBService.GetRegion(UUID); 144 TheSim = m_gridDBService.GetRegion(UUID);
145 145
146 if (!(TheSim == null)) 146 if (!(TheSim == null))
147 { 147 {
148 respstring = "<Root>"; 148 respstring = "<Root>";
149 respstring += "<authkey>" + TheSim.regionSendKey + "</authkey>"; 149 respstring += "<authkey>" + TheSim.regionSendKey + "</authkey>";
150 respstring += "<sim>"; 150 respstring += "<sim>";
151 respstring += "<uuid>" + TheSim.UUID.ToString() + "</uuid>"; 151 respstring += "<uuid>" + TheSim.UUID.ToString() + "</uuid>";
152 respstring += "<regionname>" + TheSim.regionName + "</regionname>"; 152 respstring += "<regionname>" + TheSim.regionName + "</regionname>";
153 respstring += "<sim_ip>" + TheSim.serverIP + "</sim_ip>"; 153 respstring += "<sim_ip>" + TheSim.serverIP + "</sim_ip>";
154 respstring += "<sim_port>" + TheSim.serverPort.ToString() + "</sim_port>"; 154 respstring += "<sim_port>" + TheSim.serverPort.ToString() + "</sim_port>";
155 respstring += "<region_locx>" + TheSim.regionLocX.ToString() + "</region_locx>"; 155 respstring += "<region_locx>" + TheSim.regionLocX.ToString() + "</region_locx>";
156 respstring += "<region_locy>" + TheSim.regionLocY.ToString() + "</region_locy>"; 156 respstring += "<region_locy>" + TheSim.regionLocY.ToString() + "</region_locy>";
157 respstring += "<estate_id>1</estate_id>"; 157 respstring += "<estate_id>1</estate_id>";
158 respstring += "</sim>"; 158 respstring += "</sim>";
159 respstring += "</Root>"; 159 respstring += "</Root>";
160 } 160 }
161 } 161 }
162 else 162 else
163 { 163 {
164 respstring = "<Root>"; 164 respstring = "<Root>";
165 respstring += "<error>Param must be a UUID</error>"; 165 respstring += "<error>Param must be a UUID</error>";
166 respstring += "</Root>"; 166 respstring += "</Root>";
167 } 167 }
168 168
169 return respstring; 169 return respstring;
170 } 170 }
171 171
172 /// <summary> 172 /// <summary>
173 /// Creates or updates a sim via a REST Method Request 173 /// Creates or updates a sim via a REST Method Request
174 /// BROKEN with SQL Update 174 /// BROKEN with SQL Update
175 /// </summary> 175 /// </summary>
176 /// <param name="request"></param> 176 /// <param name="request"></param>
177 /// <param name="path"></param> 177 /// <param name="path"></param>
178 /// <param name="param"></param> 178 /// <param name="param"></param>
179 /// <param name="httpRequest">HTTP request header object</param> 179 /// <param name="httpRequest">HTTP request header object</param>
180 /// <param name="httpResponse">HTTP response header object</param> 180 /// <param name="httpResponse">HTTP response header object</param>
181 /// <returns>"OK" or an error</returns> 181 /// <returns>"OK" or an error</returns>
182 public string RestSetSimMethod(string request, string path, string param, 182 public string RestSetSimMethod(string request, string path, string param,
183 OSHttpRequest httpRequest, OSHttpResponse httpResponse) 183 OSHttpRequest httpRequest, OSHttpResponse httpResponse)
184 { 184 {
185 Console.WriteLine("Processing region update via REST method"); 185 Console.WriteLine("Processing region update via REST method");
186 RegionProfileData theSim; 186 RegionProfileData theSim;
187 theSim = m_gridDBService.GetRegion(new UUID(param)); 187 theSim = m_gridDBService.GetRegion(new UUID(param));
188 if (theSim == null) 188 if (theSim == null)
189 { 189 {
190 theSim = new RegionProfileData(); 190 theSim = new RegionProfileData();
191 UUID UUID = new UUID(param); 191 UUID UUID = new UUID(param);
192 theSim.UUID = UUID; 192 theSim.UUID = UUID;
193 theSim.regionRecvKey = m_config.SimRecvKey; 193 theSim.regionRecvKey = m_config.SimRecvKey;
194 } 194 }
195 195
196 XmlDocument doc = new XmlDocument(); 196 XmlDocument doc = new XmlDocument();
197 doc.LoadXml(request); 197 doc.LoadXml(request);
198 XmlNode rootnode = doc.FirstChild; 198 XmlNode rootnode = doc.FirstChild;
199 XmlNode authkeynode = rootnode.ChildNodes[0]; 199 XmlNode authkeynode = rootnode.ChildNodes[0];
200 if (authkeynode.Name != "authkey") 200 if (authkeynode.Name != "authkey")
201 { 201 {
202 return "ERROR! bad XML - expected authkey tag"; 202 return "ERROR! bad XML - expected authkey tag";
203 } 203 }
204 204
205 XmlNode simnode = rootnode.ChildNodes[1]; 205 XmlNode simnode = rootnode.ChildNodes[1];
206 if (simnode.Name != "sim") 206 if (simnode.Name != "sim")
207 { 207 {
208 return "ERROR! bad XML - expected sim tag"; 208 return "ERROR! bad XML - expected sim tag";
209 } 209 }
210 210
211 //theSim.regionSendKey = Cfg; 211 //theSim.regionSendKey = Cfg;
212 theSim.regionRecvKey = m_config.SimRecvKey; 212 theSim.regionRecvKey = m_config.SimRecvKey;
213 theSim.regionSendKey = m_config.SimSendKey; 213 theSim.regionSendKey = m_config.SimSendKey;
214 theSim.regionSecret = m_config.SimRecvKey; 214 theSim.regionSecret = m_config.SimRecvKey;
215 theSim.regionDataURI = String.Empty; 215 theSim.regionDataURI = String.Empty;
216 theSim.regionAssetURI = m_config.DefaultAssetServer; 216 theSim.regionAssetURI = m_config.DefaultAssetServer;
217 theSim.regionAssetRecvKey = m_config.AssetRecvKey; 217 theSim.regionAssetRecvKey = m_config.AssetRecvKey;
218 theSim.regionAssetSendKey = m_config.AssetSendKey; 218 theSim.regionAssetSendKey = m_config.AssetSendKey;
219 theSim.regionUserURI = m_config.DefaultUserServer; 219 theSim.regionUserURI = m_config.DefaultUserServer;
220 theSim.regionUserSendKey = m_config.UserSendKey; 220 theSim.regionUserSendKey = m_config.UserSendKey;
221 theSim.regionUserRecvKey = m_config.UserRecvKey; 221 theSim.regionUserRecvKey = m_config.UserRecvKey;
222 222
223 for (int i = 0; i < simnode.ChildNodes.Count; i++) 223 for (int i = 0; i < simnode.ChildNodes.Count; i++)
224 { 224 {
225 switch (simnode.ChildNodes[i].Name) 225 switch (simnode.ChildNodes[i].Name)
226 { 226 {
227 case "regionname": 227 case "regionname":
228 theSim.regionName = simnode.ChildNodes[i].InnerText; 228 theSim.regionName = simnode.ChildNodes[i].InnerText;
229 break; 229 break;
230 230
231 case "sim_ip": 231 case "sim_ip":
232 theSim.serverIP = simnode.ChildNodes[i].InnerText; 232 theSim.serverIP = simnode.ChildNodes[i].InnerText;
233 break; 233 break;
234 234
235 case "sim_port": 235 case "sim_port":
236 theSim.serverPort = Convert.ToUInt32(simnode.ChildNodes[i].InnerText); 236 theSim.serverPort = Convert.ToUInt32(simnode.ChildNodes[i].InnerText);
237 break; 237 break;
238 238
239 case "region_locx": 239 case "region_locx":
240 theSim.regionLocX = Convert.ToUInt32((string)simnode.ChildNodes[i].InnerText); 240 theSim.regionLocX = Convert.ToUInt32((string)simnode.ChildNodes[i].InnerText);
241 theSim.regionHandle = Utils.UIntsToLong((theSim.regionLocX * Constants.RegionSize), (theSim.regionLocY * Constants.RegionSize)); 241 theSim.regionHandle = Utils.UIntsToLong((theSim.regionLocX * Constants.RegionSize), (theSim.regionLocY * Constants.RegionSize));
242 break; 242 break;
243 243
244 case "region_locy": 244 case "region_locy":
245 theSim.regionLocY = Convert.ToUInt32((string)simnode.ChildNodes[i].InnerText); 245 theSim.regionLocY = Convert.ToUInt32((string)simnode.ChildNodes[i].InnerText);
246 theSim.regionHandle = Utils.UIntsToLong((theSim.regionLocX * Constants.RegionSize), (theSim.regionLocY * Constants.RegionSize)); 246 theSim.regionHandle = Utils.UIntsToLong((theSim.regionLocX * Constants.RegionSize), (theSim.regionLocY * Constants.RegionSize));
247 break; 247 break;
248 } 248 }
249 } 249 }
250 250
251 theSim.serverURI = "http://" + theSim.serverIP + ":" + theSim.serverPort + "/"; 251 theSim.serverURI = "http://" + theSim.serverIP + ":" + theSim.serverPort + "/";
252 bool requirePublic = false; 252 bool requirePublic = false;
253 bool requireValid = true; 253 bool requireValid = true;
254 254
255 if (requirePublic && 255 if (requirePublic &&
256 (theSim.serverIP.StartsWith("172.16") || theSim.serverIP.StartsWith("192.168") || 256 (theSim.serverIP.StartsWith("172.16") || theSim.serverIP.StartsWith("192.168") ||
257 theSim.serverIP.StartsWith("10.") || theSim.serverIP.StartsWith("0.") || 257 theSim.serverIP.StartsWith("10.") || theSim.serverIP.StartsWith("0.") ||
258 theSim.serverIP.StartsWith("255."))) 258 theSim.serverIP.StartsWith("255.")))
259 { 259 {
260 return "ERROR! Servers must register with public addresses."; 260 return "ERROR! Servers must register with public addresses.";
261 } 261 }
262 262
263 if (requireValid && (theSim.serverIP.StartsWith("0.") || theSim.serverIP.StartsWith("255."))) 263 if (requireValid && (theSim.serverIP.StartsWith("0.") || theSim.serverIP.StartsWith("255.")))
264 { 264 {
265 return "ERROR! 0.*.*.* / 255.*.*.* Addresses are invalid, please check your server config and try again"; 265 return "ERROR! 0.*.*.* / 255.*.*.* Addresses are invalid, please check your server config and try again";
266 } 266 }
267 267
268 try 268 try
269 { 269 {
270 m_log.Info("[DATA]: " + 270 m_log.Info("[DATA]: " +
271 "Updating / adding via " + m_gridDBService.GetNumberOfPlugins() + " storage provider(s) registered."); 271 "Updating / adding via " + m_gridDBService.GetNumberOfPlugins() + " storage provider(s) registered.");
272 272
273 return m_gridDBService.CheckReservations(theSim, authkeynode); 273 return m_gridDBService.CheckReservations(theSim, authkeynode);
274 } 274 }
275 catch (Exception e) 275 catch (Exception e)
276 { 276 {
277 return "ERROR! Could not save to database! (" + e.ToString() + ")"; 277 return "ERROR! Could not save to database! (" + e.ToString() + ")";
278 } 278 }
279 } 279 }
280 } 280 }
281} 281}