/*
* 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.Threading;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Reflection;
using System.Timers;
using System.Xml;
using libsecondlife;
using Mono.Addins;
using Nwc.XmlRpc;
using Nini.Config;
using OpenSim.Framework;
using OpenSim.Framework.Console;
using OpenSim.Framework.Servers;
using OpenSim.Framework.Communications;
using OpenSim.Region.Environment.Scenes;
// [assembly : Addin]
// [assembly : AddinDependency("OpenSim", "0.5")]
namespace OpenSim.ApplicationPlugins.Rest
{
// [Extension("/OpenSim/Startup")]
public abstract class RestPlugin : IApplicationPlugin
{
#region properties
protected static readonly log4net.ILog m_log =
log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private IConfig _config; // Configuration source: Rest Plugins
private IConfig _pluginConfig; // Configuration source: Plugin specific
private OpenSimMain _app; // The 'server'
private BaseHttpServer _httpd; // The server's RPC interface
private string _prefix; // URL prefix below
// which all REST URLs
// are living
private StringWriter _sw = null;
private XmlTextWriter _xw = null;
private string _godkey;
private int _reqk;
[ThreadStaticAttribute]
private static string _threadRequestID = String.Empty;
///
/// Return an ever increasing request ID for logging
///
protected string RequestID
{
get { return _reqk++.ToString(); }
set { _reqk = Convert.ToInt32(value); }
}
///
/// Thread-constant message IDs for logging.
///
protected string MsgID
{
get { return String.Format("[REST-{0}] #{1}", Name, _threadRequestID); }
set { _threadRequestID = value; }
}
///
/// Returns true if Rest Plugins are enabled.
///
public bool PluginsAreEnabled
{
get { return null != _config; }
}
///
/// Returns true if specific Rest Plugin is enabled.
///
public bool IsEnabled
{
get
{
return (null != _pluginConfig) && _pluginConfig.GetBoolean("enabled", false);
}
}
///
/// OpenSimMain application
///
public OpenSimMain App
{
get { return _app; }
}
///
/// RPC server
///
public BaseHttpServer HttpServer
{
get { return _httpd; }
}
///
/// URL prefix to use for all REST handlers
///
public string Prefix
{
get { return _prefix; }
}
///
/// Configuration of the plugin
///
public IConfig Config
{
get { return _pluginConfig; }
}
///
/// Name of the plugin
///
public abstract string Name { get; }
///
/// Return the config section name
///
public abstract string ConfigName { get; }
public XmlTextWriter XmlWriter
{
get {
if (null == _xw)
{
_sw = new StringWriter();
_xw = new XmlTextWriter(_sw);
_xw.Formatting = Formatting.Indented;
}
return _xw; }
}
public string XmlWriterResult
{
get
{
_xw.Flush();
_xw = null;
return _sw.ToString();
}
}
#endregion properties
#region methods
///
/// This method is called by OpenSimMain immediately after loading the
/// plugin and after basic server setup, but before running any server commands.
///
///
/// Note that entries MUST be added to the active configuration files before
/// the plugin can be enabled.
///
public virtual void Initialise(OpenSimMain openSim)
{
RequestID = "0";
MsgID = RequestID;
try
{
if ((_config = openSim.ConfigSource.Configs["RestPlugins"]) == null) {
m_log.WarnFormat("{0} Rest Plugins not configured", MsgID);
return;
}
if (!_config.GetBoolean("enabled", false))
{
m_log.WarnFormat("{0} Rest Plugins are disabled", MsgID);
return;
}
_app = openSim;
_httpd = openSim.HttpServer;
// Retrieve GOD key value, if any.
_godkey = _config.GetString("god_key", String.Empty);
// Retrive prefix if any.
_prefix = _config.GetString("prefix", "/admin");
// Get plugin specific config
_pluginConfig = openSim.ConfigSource.Configs[ConfigName];
m_log.InfoFormat("{0} Rest Plugins Enabled", MsgID);
}
catch (Exception e)
{
// we can safely ignore this, as it just means that
// the key lookup in Configs failed, which signals to
// us that noone is interested in our services...they
// don't know what they are missing out on...
// NOTE: Under the present OpenSim implementation it is
// not possible for the openSim pointer to be null. However
// were the implementation to be changed, this could
// result in a silent initialization failure. Harmless
// except for lack of function and lack of any
// diagnostic indication as to why. The same is true if
// the HTTP server reference is bad.
// We should at least issue a message...
m_log.WarnFormat("{0} Initialization failed: {1}", MsgID, e.Message);
m_log.DebugFormat("{0} Initialization failed: {1}", MsgID, e.ToString());
}
}
private List _handlers = new List();
private Dictionary _agents = new Dictionary();
///
/// Add a REST stream handler to the underlying HTTP server.
///
/// GET/PUT/POST/DELETE or
/// similar
/// URL prefix
/// RestMethod handler doing the actual work
public virtual void AddRestStreamHandler(string httpMethod, string path, RestMethod method)
{
if (!IsEnabled) return;
if (!path.StartsWith(_prefix))
{
path = String.Format("{0}{1}", _prefix, path);
}
RestStreamHandler h = new RestStreamHandler(httpMethod, path, method);
_httpd.AddStreamHandler(h);
_handlers.Add(h);
m_log.DebugFormat("{0} Added REST handler {1} {2}", MsgID, httpMethod, path);
}
///
/// Add a powerful Agent handler to the underlying HTTP
/// server.
///
/// name of agent handler
/// agent handler method
/// false when the plugin is disabled or the agent
/// handler could not be added. Any generated exceptions are
/// allowed to drop through to the caller, i.e. ArgumentException.
///
public bool AddAgentHandler(string agentName, IHttpAgentHandler handler)
{
if (!IsEnabled) return false;
_agents.Add(agentName, handler);
return _httpd.AddAgentHandler(agentName, handler);
}
///
/// Remove a powerful Agent handler from the underlying HTTP
/// server.
///
/// name of agent handler
/// agent handler method
/// false when the plugin is disabled or the agent
/// handler could not be removed. Any generated exceptions are
/// allowed to drop through to the caller, i.e. KeyNotFound.
///
public bool RemoveAgentHandler(string agentName, IHttpAgentHandler handler)
{
if (!IsEnabled) return false;
if(_agents[agentName] == handler)
{
_agents.Remove(agentName);
return _httpd.RemoveAgentHandler(agentName, handler);
}
return false;
}
///
/// Check whether the HTTP request came from god; that is, is
/// the god_key as configured in the config section supplied
/// via X-OpenSim-Godkey?
///
/// HTTP request header
/// true when the HTTP request came from god.
protected bool IsGod(OSHttpRequest request)
{
string[] keys = request.Headers.GetValues("X-OpenSim-Godkey");
if (null == keys) return false;
// we take the last key supplied
return keys[keys.Length-1] == _godkey;
}
///
/// Checks wether the X-OpenSim-Password value provided in the
/// HTTP header is indeed the password on file for the avatar
/// specified by the UUID
///
protected bool IsVerifiedUser(OSHttpRequest request, LLUUID uuid)
{
// XXX under construction
return false;
}
///
/// Clean up and remove all handlers that were added earlier.
///
public virtual void Close()
{
foreach (RestStreamHandler h in _handlers)
{
_httpd.RemoveStreamHandler(h.HttpMethod, h.Path);
}
_handlers = null;
foreach (KeyValuePair h in _agents)
{
_httpd.RemoveAgentHandler(h.Key,h.Value);
}
_agents = null;
}
///
/// Return a failure message.
///
/// origin of the failure message
/// failure message
/// This should probably set a return code as
/// well. (?)
protected string Failure(OSHttpResponse response, OSHttpStatusCode status,
string method, string format, params string[] msg)
{
string m = String.Format(format, msg);
response.StatusCode = (int)status;
response.StatusDescription = m;
m_log.ErrorFormat("{0} {1} failed: {2}", MsgID, method, m);
return String.Format("{0}", m);
}
///
/// Return a failure message.
///
/// origin of the failure message
/// exception causing the failure message
/// This should probably set a return code as
/// well. (?)
public string Failure(OSHttpResponse response, OSHttpStatusCode status,
string method, Exception e)
{
string m = String.Format("exception occurred: {0}", e.Message);
response.StatusCode = (int)status;
response.StatusDescription = m;
m_log.DebugFormat("{0} {1} failed: {2}", MsgID, method, e.ToString());
m_log.ErrorFormat("{0} {1} failed: {2}", MsgID, method, e.Message);
return String.Format("{0}", e.Message);
}
#endregion methods
}
}