OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
SimianAssetServiceConnector.cs
Go to the documentation of this file.
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 OpenSimulator 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;
29 using System.Collections.Generic;
30 using System.Collections.Specialized;
31 using System.IO;
32 using System.Net;
33 using System.Reflection;
34 using log4net;
35 using Mono.Addins;
36 using Nini.Config;
37 using OpenSim.Framework;
38 using OpenSim.Region.Framework.Interfaces;
39 using OpenSim.Region.Framework.Scenes;
40 using OpenSim.Services.Interfaces;
41 using OpenMetaverse;
42 using OpenMetaverse.StructuredData;
43 
44 namespace OpenSim.Services.Connectors.SimianGrid
45 {
49  [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "SimianAssetServiceConnector")]
51  {
52  private static readonly ILog m_log =
53  LogManager.GetLogger(
54  MethodBase.GetCurrentMethod().DeclaringType);
55  private static string ZeroID = UUID.Zero.ToString();
56 
57  private string m_serverUrl = String.Empty;
58  private IImprovedAssetCache m_cache;
59  private bool m_Enabled = false;
60 
61  #region ISharedRegionModule
62 
63  public Type ReplaceableInterface { get { return null; } }
64  public void RegionLoaded(Scene scene)
65  {
66  if (m_cache == null)
67  {
68  IImprovedAssetCache cache = scene.RequestModuleInterface<IImprovedAssetCache>();
69  if (cache is ISharedRegionModule)
70  m_cache = cache;
71  }
72  }
73  public void PostInitialise() { }
74  public void Close() { }
75 
77  public string Name { get { return "SimianAssetServiceConnector"; } }
78  public void AddRegion(Scene scene) { if (m_Enabled) { scene.RegisterModuleInterface<IAssetService>(this); } }
79  public void RemoveRegion(Scene scene) { if (m_Enabled) { scene.UnregisterModuleInterface<IAssetService>(this); } }
80 
81  #endregion ISharedRegionModule
82 
83  public SimianAssetServiceConnector(IConfigSource source)
84  {
85  CommonInit(source);
86  }
87 
88  public SimianAssetServiceConnector(string url)
89  {
90  if (!url.EndsWith("/") && !url.EndsWith("="))
91  url = url + '/';
92  m_serverUrl = url;
93  }
94 
95  public void Initialise(IConfigSource source)
96  {
97  IConfig moduleConfig = source.Configs["Modules"];
98  if (moduleConfig != null)
99  {
100  string name = moduleConfig.GetString("AssetServices", "");
101  if (name == Name)
102  CommonInit(source);
103  }
104  }
105 
106  private void CommonInit(IConfigSource source)
107  {
108  IConfig gridConfig = source.Configs["AssetService"];
109  if (gridConfig != null)
110  {
111  string serviceUrl = gridConfig.GetString("AssetServerURI");
112  if (!String.IsNullOrEmpty(serviceUrl))
113  {
114  if (!serviceUrl.EndsWith("/") && !serviceUrl.EndsWith("="))
115  serviceUrl = serviceUrl + '/';
116  m_serverUrl = serviceUrl;
117  }
118  }
119 
120  if (String.IsNullOrEmpty(m_serverUrl))
121  m_log.Info("[SIMIAN ASSET CONNECTOR]: No AssetServerURI specified, disabling connector");
122  else
123  m_Enabled = true;
124  }
125 
126 #region IAssetService
127 
128  public AssetBase Get(string id)
129  {
130  if (String.IsNullOrEmpty(m_serverUrl))
131  {
132  m_log.Error("[SIMIAN ASSET CONNECTOR]: No AssetServerURI configured");
133  throw new InvalidOperationException();
134  }
135 
136  // Cache fetch
137  if (m_cache != null)
138  {
139  AssetBase asset = m_cache.Get(id);
140  if (asset != null)
141  return asset;
142  }
143 
144  return SimianGetOperation(id);
145  }
146 
147 
148  public AssetBase GetCached(string id)
149  {
150  if (m_cache != null)
151  return m_cache.Get(id);
152 
153  return null;
154  }
155 
161  public AssetMetadata GetMetadata(string id)
162  {
163  if (String.IsNullOrEmpty(m_serverUrl))
164  {
165  m_log.Error("[SIMIAN ASSET CONNECTOR]: No AssetServerURI configured");
166  throw new InvalidOperationException();
167  }
168 
169  // Cache fetch
170  if (m_cache != null)
171  {
172  AssetBase asset = m_cache.Get(id);
173  if (asset != null)
174  return asset.Metadata;
175  }
176 
177  // return GetRemoteMetadata(id);
178  return SimianGetMetadataOperation(id);
179  }
180 
181  public byte[] GetData(string id)
182  {
183  if (String.IsNullOrEmpty(m_serverUrl))
184  {
185  m_log.Error("[SIMIAN ASSET CONNECTOR]: No AssetServerURI configured");
186  throw new InvalidOperationException();
187  }
188 
189  AssetBase asset = Get(id);
190 
191  if (asset != null)
192  return asset.Data;
193 
194  return null;
195  }
196 
204  public bool Get(string id, Object sender, AssetRetrieved handler)
205  {
206  if (String.IsNullOrEmpty(m_serverUrl))
207  {
208  m_log.Error("[SIMIAN ASSET CONNECTOR]: No AssetServerURI configured");
209  throw new InvalidOperationException();
210  }
211 
212  // Cache fetch
213  if (m_cache != null)
214  {
215  AssetBase asset = m_cache.Get(id);
216  if (asset != null)
217  {
218  handler(id, sender, asset);
219  return true;
220  }
221  }
222 
223  Util.FireAndForget(
224  delegate(object o)
225  {
226  AssetBase asset = SimianGetOperation(id);
227  handler(id, sender, asset);
228  }, null, "SimianAssetServiceConnector.GetFromService"
229  );
230 
231  return true;
232  }
233 
234  public bool[] AssetsExist(string[] ids)
235  {
236  if (String.IsNullOrEmpty(m_serverUrl))
237  {
238  m_log.Error("[SIMIAN ASSET CONNECTOR]: No AssetServerURI configured");
239  throw new InvalidOperationException();
240  }
241 
242  bool[] exist = new bool[ids.Length];
243 
244  for (int i = 0; i < ids.Length; i++)
245  {
246  AssetMetadata metadata = GetMetadata(ids[i]);
247  if (metadata != null)
248  exist[i] = true;
249  }
250 
251  return exist;
252  }
253 
260  public string Store(AssetBase asset)
261  {
262  if (String.IsNullOrEmpty(m_serverUrl))
263  {
264  m_log.Error("[SIMIAN ASSET CONNECTOR]: No AssetServerURI configured");
265  throw new InvalidOperationException();
266  }
267 
268  bool storedInCache = false;
269 
270  // AssetID handling
271  if (String.IsNullOrEmpty(asset.ID) || asset.ID == ZeroID)
272  {
273  asset.FullID = UUID.Random();
274  asset.ID = asset.FullID.ToString();
275  }
276 
277  // Cache handling
278  if (m_cache != null)
279  {
280  m_cache.Cache(asset);
281  storedInCache = true;
282  }
283 
284  // Local asset handling
285  if (asset.Local)
286  {
287  if (!storedInCache)
288  {
289  m_log.Error("Cannot store local " + asset.Metadata.ContentType + " asset without an asset cache");
290  asset.ID = null;
291  asset.FullID = UUID.Zero;
292  }
293 
294  return asset.ID;
295  }
296 
297  return SimianStoreOperation(asset);
298  }
299 
307  public bool UpdateContent(string id, byte[] data)
308  {
309  if (String.IsNullOrEmpty(m_serverUrl))
310  {
311  m_log.Error("[SIMIAN ASSET CONNECTOR]: No AssetServerURI configured");
312  throw new InvalidOperationException();
313  }
314 
315  AssetBase asset = Get(id);
316 
317  if (asset == null)
318  {
319  m_log.WarnFormat("[SIMIAN ASSET CONNECTOR]: Failed to fetch asset {0} for updating", id);
320  return false;
321  }
322 
323  asset.Data = data;
324 
325  string result = Store(asset);
326  return !String.IsNullOrEmpty(result);
327  }
328 
334  public bool Delete(string id)
335  {
336  if (String.IsNullOrEmpty(m_serverUrl))
337  {
338  m_log.Error("[SIMIAN ASSET CONNECTOR]: No AssetServerURI configured");
339  throw new InvalidOperationException();
340  }
341 
342  if (m_cache != null)
343  m_cache.Expire(id);
344 
345  return SimianDeleteOperation(id);
346  }
347 
348 #endregion IAssetService
349 
350 #region SimianOperations
351  private bool SimianDeleteOperation(string id)
357  {
358  try
359  {
360  NameValueCollection requestArgs = new NameValueCollection
361  {
362  { "RequestMethod", "xRemoveAsset" },
363  { "AssetID", id }
364  };
365 
366  OSDMap response = SimianGrid.PostToService(m_serverUrl,requestArgs);
367  if (! response["Success"].AsBoolean())
368  {
369  m_log.WarnFormat("[SIMIAN ASSET CONNECTOR]: failed to delete asset; {0}",response["Message"].AsString());
370  return false;
371  }
372 
373  return true;
374 
375  }
376  catch (Exception ex)
377  {
378  m_log.WarnFormat("[SIMIAN ASSET CONNECTOR]: failed to delete asset {0}; {1}", id, ex.Message);
379  }
380 
381  return false;
382  }
383 
389  private string SimianStoreOperation(AssetBase asset)
390  {
391  try
392  {
393  NameValueCollection requestArgs = new NameValueCollection
394  {
395  { "RequestMethod", "xAddAsset" },
396  { "ContentType", asset.Metadata.ContentType },
397  { "EncodedData", Convert.ToBase64String(asset.Data) },
398  { "AssetID", asset.FullID.ToString() },
399  { "CreatorID", asset.Metadata.CreatorID },
400  { "Temporary", asset.Temporary ? "1" : "0" },
401  { "Name", asset.Name }
402  };
403 
404  OSDMap response = SimianGrid.PostToService(m_serverUrl,requestArgs);
405  if (! response["Success"].AsBoolean())
406  {
407  m_log.WarnFormat("[SIMIAN ASSET CONNECTOR] failed to store asset; {0}",response["Message"].AsString());
408  return null;
409  }
410 
411  // asset.ID is always set before calling this function
412  return asset.ID;
413 
414  }
415  catch (Exception ex)
416  {
417  m_log.ErrorFormat("[SIMIAN ASSET CONNECTOR] failed to store asset; {0}",ex.Message);
418  }
419 
420  return null;
421  }
422 
428  private AssetBase SimianGetOperation(string id)
429  {
430  try
431  {
432  NameValueCollection requestArgs = new NameValueCollection
433  {
434  { "RequestMethod", "xGetAsset" },
435  { "ID", id }
436  };
437 
438  OSDMap response = SimianGrid.PostToService(m_serverUrl,requestArgs);
439  if (! response["Success"].AsBoolean())
440  {
441  m_log.WarnFormat("[SIMIAN ASSET CONNECTOR] Failed to get asset; {0}",response["Message"].AsString());
442  return null;
443  }
444 
445  AssetBase asset = new AssetBase();
446 
447  asset.ID = id;
448  asset.Name = String.Empty;
449  asset.Metadata.ContentType = response["ContentType"].AsString(); // this will also set the asset Type property
450  asset.CreatorID = response["CreatorID"].AsString();
451  asset.Data = System.Convert.FromBase64String(response["EncodedData"].AsString());
452  asset.Local = false;
453  asset.Temporary = response["Temporary"];
454 
455  return asset;
456  }
457  catch (Exception ex)
458  {
459  m_log.WarnFormat("[SIMIAN ASSET CONNECTOR]: failed to retrieve asset {0}; {1}", id, ex.Message);
460  }
461 
462  return null;
463  }
464 
471  private AssetMetadata SimianGetMetadataOperation(string id)
472  {
473  try
474  {
475  NameValueCollection requestArgs = new NameValueCollection
476  {
477  { "RequestMethod", "xGetAssetMetadata" },
478  { "ID", id }
479  };
480 
481  OSDMap response = SimianGrid.PostToService(m_serverUrl,requestArgs);
482  if (! response["Success"].AsBoolean())
483  {
484  // this is not really an error, this call is used to test existence
485  // m_log.DebugFormat("[SIMIAN ASSET CONNECTOR] Failed to get asset metadata; {0}",response["Message"].AsString());
486  return null;
487  }
488 
489  AssetMetadata metadata = new AssetMetadata();
490  metadata.ID = id;
491  metadata.ContentType = response["ContentType"].AsString();
492  metadata.CreatorID = response["CreatorID"].AsString();
493  metadata.Local = false;
494  metadata.Temporary = response["Temporary"];
495 
496  string lastModifiedStr = response["Last-Modified"].AsString();
497  if (! String.IsNullOrEmpty(lastModifiedStr))
498  {
499  DateTime lastModified;
500  if (DateTime.TryParse(lastModifiedStr, out lastModified))
501  metadata.CreationDate = lastModified;
502  }
503 
504  return metadata;
505  }
506  catch (Exception ex)
507  {
508  m_log.WarnFormat("[SIMIAN ASSET CONNECTOR]: Failed to get asset metadata; {0}", ex.Message);
509  }
510 
511  return null;
512  }
513 #endregion
514 
515  // private AssetMetadata GetRemoteMetadata(string id)
516  // {
517  // Uri url;
518  // AssetMetadata metadata = null;
519 
520  // // Determine if id is an absolute URL or a grid-relative UUID
521  // if (!Uri.TryCreate(id, UriKind.Absolute, out url))
522  // url = new Uri(m_serverUrl + id);
523 
524  // try
525  // {
526  // HttpWebRequest request = UntrustedHttpWebRequest.Create(url);
527  // request.Method = "HEAD";
528 
529  // using (WebResponse response = request.GetResponse())
530  // {
531  // using (Stream responseStream = response.GetResponseStream())
532  // {
533  // // Create the metadata object
534  // metadata = new AssetMetadata();
535  // metadata.ContentType = response.ContentType;
536  // metadata.ID = id;
537 
538  // UUID uuid;
539  // if (UUID.TryParse(id, out uuid))
540  // metadata.FullID = uuid;
541 
542  // string lastModifiedStr = response.Headers.Get("Last-Modified");
543  // if (!String.IsNullOrEmpty(lastModifiedStr))
544  // {
545  // DateTime lastModified;
546  // if (DateTime.TryParse(lastModifiedStr, out lastModified))
547  // metadata.CreationDate = lastModified;
548  // }
549  // }
550  // }
551  // }
552  // catch (Exception ex)
553  // {
554  // m_log.Warn("[SIMIAN ASSET CONNECTOR]: Asset HEAD from " + url + " failed: " + ex.Message);
555  // }
556 
557  // return metadata;
558  // }
559 
560  // private AssetBase GetRemote(string id)
561  // {
562  // AssetBase asset = null;
563  // Uri url;
564 
565  // // Determine if id is an absolute URL or a grid-relative UUID
566  // if (!Uri.TryCreate(id, UriKind.Absolute, out url))
567  // url = new Uri(m_serverUrl + id);
568 
569  // try
570  // {
571  // HttpWebRequest request = UntrustedHttpWebRequest.Create(url);
572 
573  // using (WebResponse response = request.GetResponse())
574  // {
575  // using (Stream responseStream = response.GetResponseStream())
576  // {
577  // string creatorID = response.Headers.GetOne("X-Asset-Creator-Id") ?? String.Empty;
578 
579  // // Create the asset object
580  // asset = new AssetBase(id, String.Empty, SLUtil.ContentTypeToSLAssetType(response.ContentType), creatorID);
581 
582  // UUID assetID;
583  // if (UUID.TryParse(id, out assetID))
584  // asset.FullID = assetID;
585 
586  // // Grab the asset data from the response stream
587  // using (MemoryStream stream = new MemoryStream())
588  // {
589  // responseStream.CopyStream(stream, Int32.MaxValue);
590  // asset.Data = stream.ToArray();
591  // }
592  // }
593  // }
594 
595  // // Cache store
596  // if (m_cache != null && asset != null)
597  // m_cache.Cache(asset);
598 
599  // return asset;
600  // }
601  // catch (Exception ex)
602  // {
603  // m_log.Warn("[SIMIAN ASSET CONNECTOR]: Asset GET from " + url + " failed: " + ex.Message);
604  // return null;
605  // }
606  // }
607 
608  // private string StoreRemote(AssetBase asset)
609  // {
610  // // Distinguish public and private assets
611  // bool isPublic = true;
612  // switch ((AssetType)asset.Type)
613  // {
614  // case AssetType.CallingCard:
615  // case AssetType.Gesture:
616  // case AssetType.LSLBytecode:
617  // case AssetType.LSLText:
618  // isPublic = false;
619  // break;
620  // }
621 
622  // string errorMessage = null;
623 
624  // // Build the remote storage request
625  // List<MultipartForm.Element> postParameters = new List<MultipartForm.Element>()
626  // {
627  // new MultipartForm.Parameter("AssetID", asset.FullID.ToString()),
628  // new MultipartForm.Parameter("CreatorID", asset.Metadata.CreatorID),
629  // new MultipartForm.Parameter("Temporary", asset.Temporary ? "1" : "0"),
630  // new MultipartForm.Parameter("Public", isPublic ? "1" : "0"),
631  // new MultipartForm.File("Asset", asset.Name, asset.Metadata.ContentType, asset.Data)
632  // };
633 
634  // // Make the remote storage request
635  // try
636  // {
637  // // Simian does not require the asset ID to be in the URL because it's in the post data.
638  // // By appending it to the URL also, we allow caching proxies (squid) to invalidate asset URLs
639  // HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(m_serverUrl + asset.FullID.ToString());
640 
641  // using (HttpWebResponse response = MultipartForm.Post(request, postParameters))
642  // {
643  // using (Stream responseStream = response.GetResponseStream())
644  // {
645  // string responseStr = null;
646 
647  // try
648  // {
649  // responseStr = responseStream.GetStreamString();
650  // OSD responseOSD = OSDParser.Deserialize(responseStr);
651  // if (responseOSD.Type == OSDType.Map)
652  // {
653  // OSDMap responseMap = (OSDMap)responseOSD;
654  // if (responseMap["Success"].AsBoolean())
655  // return asset.ID;
656  // else
657  // errorMessage = "Upload failed: " + responseMap["Message"].AsString();
658  // }
659  // else
660  // {
661  // errorMessage = "Response format was invalid:\n" + responseStr;
662  // }
663  // }
664  // catch (Exception ex)
665  // {
666  // if (!String.IsNullOrEmpty(responseStr))
667  // errorMessage = "Failed to parse the response:\n" + responseStr;
668  // else
669  // errorMessage = "Failed to retrieve the response: " + ex.Message;
670  // }
671  // }
672  // }
673  // }
674  // catch (WebException ex)
675  // {
676  // errorMessage = ex.Message;
677  // }
678 
679  // m_log.WarnFormat("[SIMIAN ASSET CONNECTOR]: Failed to store asset \"{0}\" ({1}, {2}): {3}",
680  // asset.Name, asset.ID, asset.Metadata.ContentType, errorMessage);
681 
682  // return null;
683  // }
684  }
685 }
void Close()
This is the inverse to Initialise. After a Close(), this instance won't be usable anymore...
bool[] AssetsExist(string[] ids)
Check if assets exist in the database.
void RemoveRegion(Scene scene)
This is called whenever a Scene is removed. For shared modules, this can happen several times...
bool Get(string id, Object sender, AssetRetrieved handler)
Get an asset asynchronously
OpenMetaverse.StructuredData.OSDMap OSDMap
byte[] GetData(string id)
Get an asset's data, ignoring the metadata.
void AddRegion(Scene scene)
This is called whenever a Scene is added. For shared modules, this can happen several times...
bool Local
Is this a region only asset, or does this exist on the asset server also
Definition: AssetBase.cs:213
Asset class. All Assets are reference by this class or a class derived from this class ...
Definition: AssetBase.cs:49
void Initialise(IConfigSource source)
This is called to initialize the region module. For shared modules, this is called exactly once...
void RegionLoaded(Scene scene)
This will be called once for every scene loaded. In a shared module this will be multiple times in on...
bool UpdateContent(string id, byte[] data)
Update an asset's content
void PostInitialise()
This is called exactly once after all the shared region-modules have been instanciated and IRegionMod...
delegate void AssetRetrieved(string id, Object sender, AssetBase asset)
string ID
Asset MetaData ID (transferring from UUID to string ID)
Definition: AssetBase.cs:177
AssetBase GetCached(string id)
Synchronously fetches an asset from the local cache only.