JSONRPC: Initial commit for JSON schema service description based JSON RPC handling
authormontellese <montellese@xbmc.org>
Sun, 30 Jan 2011 12:29:45 +0000 (13:29 +0100)
committermontellese <montellese@xbmc.org>
Fri, 1 Apr 2011 18:38:27 +0000 (20:38 +0200)
15 files changed:
project/VS2010Express/XBMC.vcxproj
project/VS2010Express/XBMC.vcxproj.filters
xbmc/Application.cpp
xbmc/interfaces/json-rpc/AVPlaylistOperations.cpp
xbmc/interfaces/json-rpc/AudioLibrary.cpp
xbmc/interfaces/json-rpc/FileItemHandler.cpp
xbmc/interfaces/json-rpc/JSONRPC.cpp
xbmc/interfaces/json-rpc/JSONRPC.h
xbmc/interfaces/json-rpc/JSONServiceDescription.cpp [new file with mode: 0644]
xbmc/interfaces/json-rpc/JSONServiceDescription.h [new file with mode: 0644]
xbmc/interfaces/json-rpc/JSONUtils.h
xbmc/interfaces/json-rpc/Makefile
xbmc/interfaces/json-rpc/PlaylistOperations.cpp
xbmc/interfaces/json-rpc/ServiceDescription.h [new file with mode: 0644]
xbmc/interfaces/json-rpc/VideoLibrary.cpp

index 45b59e5..c64d0cf 100644 (file)
     <ClCompile Include="..\..\xbmc\interfaces\json-rpc\FileItemHandler.cpp" />
     <ClCompile Include="..\..\xbmc\interfaces\json-rpc\FileOperations.cpp" />
     <ClCompile Include="..\..\xbmc\interfaces\json-rpc\JSONRPC.cpp" />
+    <ClCompile Include="..\..\xbmc\interfaces\json-rpc\JSONServiceDescription.cpp" />
     <ClCompile Include="..\..\xbmc\interfaces\json-rpc\PicturePlayerOperations.cpp" />
     <ClCompile Include="..\..\xbmc\interfaces\json-rpc\PlayerOperations.cpp" />
     <ClCompile Include="..\..\xbmc\interfaces\json-rpc\PlaylistOperations.cpp" />
     <ClInclude Include="..\..\xbmc\interfaces\json-rpc\IClient.h" />
     <ClInclude Include="..\..\xbmc\interfaces\json-rpc\ITransportLayer.h" />
     <ClInclude Include="..\..\xbmc\interfaces\json-rpc\JSONRPC.h" />
+    <ClInclude Include="..\..\xbmc\interfaces\json-rpc\JSONServiceDescription.h" />
     <ClInclude Include="..\..\xbmc\interfaces\json-rpc\JSONUtils.h" />
     <ClInclude Include="..\..\xbmc\interfaces\json-rpc\PicturePlayerOperations.h" />
     <ClInclude Include="..\..\xbmc\interfaces\json-rpc\PlayerOperations.h" />
     <ClInclude Include="..\..\xbmc\interfaces\json-rpc\PlaylistOperations.h" />
+    <ClInclude Include="..\..\xbmc\interfaces\json-rpc\ServiceDescription.h" />
     <ClInclude Include="..\..\xbmc\interfaces\json-rpc\SystemOperations.h" />
     <ClInclude Include="..\..\xbmc\interfaces\json-rpc\VideoLibrary.h" />
     <ClInclude Include="..\..\xbmc\interfaces\json-rpc\XBMCOperations.h" />
       <UserProperties RESOURCE_FILE="XBMC_PC.rc" />
     </VisualStudio>
   </ProjectExtensions>
-</Project>
\ No newline at end of file
+</Project>
index 1b81bac..5ae5b48 100644 (file)
     <ClCompile Include="..\..\xbmc\dialogs\GUIDialogPlayEject.cpp">
       <Filter>dialogs</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\xbmc\interfaces\json-rpc\JSONServiceDescription.cpp">
+      <Filter>interfaces\json-rpc</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\..\xbmc\win32\pch.h">
     <ClInclude Include="..\..\xbmc\dialogs\GUIDialogPlayEject.h">
       <Filter>dialogs</Filter>
     </ClInclude>
+    <ClInclude Include="..\..\xbmc\interfaces\json-rpc\JSONServiceDescription.h">
+      <Filter>interfaces\json-rpc</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\xbmc\interfaces\json-rpc\ServiceDescription.h">
+      <Filter>interfaces\json-rpc</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="..\..\xbmc\win32\XBMC.ico">
index 4232cf0..68d4d74 100644 (file)
@@ -1255,6 +1255,8 @@ void CApplication::StartJSONRPCServer()
 #ifdef HAS_JSONRPC
   if (g_guiSettings.GetBool("services.esenabled"))
   {
+    CJSONRPC::Initialize();
+
     if (CTCPServer::StartServer(g_advancedSettings.m_jsonTcpPort, g_guiSettings.GetBool("services.esallinterfaces")))
       CZeroconf::GetInstance()->PublishService("servers.jsonrpc", "_xbmc-jsonrpc._tcp", "XBMC JSONRPC", g_advancedSettings.m_jsonTcpPort);
   }
index 3925cef..2f1df61 100644 (file)
@@ -112,10 +112,8 @@ JSON_STATUS CAVPlaylistOperations::Insert(const CStdString &method, ITransportLa
   if (!FillFileItemList(parameterObject, list))
     return InvalidParams;
 
-  const Value param = ForceObject(parameterObject);
-
-  if (param["index"].isInt())
-          indexValue = param["index"].asInt();
+ if (parameterObject["index"].isInt())
+          indexValue = parameterObject["index"].asInt();
 
   g_application.getApplicationMessenger().PlayListPlayerInsert(GetPlaylist(method), list, indexValue);
 
index 11e3ca7..3917cc8 100644 (file)
@@ -36,19 +36,18 @@ JSON_STATUS CAudioLibrary::GetArtists(const CStdString &method, ITransportLayer
   if (!(parameterObject.isObject() || parameterObject.isNull()))
     return InvalidParams;
 
-  const Value param = ForceObject(parameterObject);
-  if (!ParameterIntOrNull(param, "genreid"))
-    return InvalidParams;
+  //if (!ParameterIntOrNull(parameterObject, "genreid"))
+  //  return InvalidParams;
 
   CMusicDatabase musicdatabase;
   if (!musicdatabase.Open())
     return InternalError;
 
-  int genreID = ParameterAsInt(param, -1, "genreid");
+  int genreID = parameterObject.get("genreid", -1).asInt();
 
   CFileItemList items;
   if (musicdatabase.GetArtistsNav("", items, genreID, false))
-    HandleFileItemList("artistid", false, "artists", items, param, result);
+    HandleFileItemList("artistid", false, "artists", items, parameterObject, result);
 
   musicdatabase.Close();
   return OK;
@@ -58,23 +57,22 @@ JSON_STATUS CAudioLibrary::GetAlbums(const CStdString &method, ITransportLayer *
 {
   if (!(parameterObject.isObject() || parameterObject.isNull()))
     return InvalidParams;
-
-  const Value param = ForceObject(parameterObject);
-  if (!(ParameterIntOrNull(param, "artistid") || ParameterIntOrNull(param, "genreid")))
-    return InvalidParams;
+    
+  //if (!(ParameterIntOrNull(parameterObject, "artistid") || ParameterIntOrNull(parameterObject, "albumid")))
+  //  return InvalidParams;
 
   CMusicDatabase musicdatabase;
   if (!musicdatabase.Open())
     return InternalError;
 
-  int artistID = ParameterAsInt(param, -1, "artistid");
-  int genreID  = ParameterAsInt(param, -1, "genreid");
-  int start = ParameterAsInt(param, -1, "start");
-  int end = ParameterAsInt(param, -1, "end");
+  int artistID = parameterObject.get("artistid", -1).asInt();
+  int genreID  = parameterObject.get("genreid", -1).asInt();
+  int start = parameterObject.get("start", -1).asInt();
+  int end = parameterObject.get("end", -1).asInt();
 
   CFileItemList items;
   if (musicdatabase.GetAlbumsNav("", items, genreID, artistID, start, end))
-    HandleFileItemList("albumid", false, "albums", items, param, result);
+    HandleFileItemList("albumid", false, "albums", items, parameterObject, result);
 
   musicdatabase.Close();
   return OK;
@@ -85,11 +83,10 @@ JSON_STATUS CAudioLibrary::GetAlbumDetails(const CStdString &method, ITransportL
   if (!(parameterObject.isObject() || parameterObject.isNull()))
     return InvalidParams;
 
-  const Value param = ForceObject(parameterObject);
-  if (!(ParameterIntOrNull(param, "albumid")))
-    return InvalidParams;
+  //if (!(ParameterIntOrNull(parameterObject, "albumid")))
+  //  return InvalidParams;
 
-  int albumID = ParameterAsInt(param, -1, "albumid");
+  int albumID = parameterObject.get("albumid", -1).asInt();
   if (albumID <= 0)
     return InvalidParams;
 
@@ -133,21 +130,20 @@ JSON_STATUS CAudioLibrary::GetSongs(const CStdString &method, ITransportLayer *t
   if (!(parameterObject.isObject() || parameterObject.isNull()))
     return InvalidParams;
 
-  const Value param = ForceObject(parameterObject);
-  if (!(ParameterIntOrNull(param, "artistid") || ParameterIntOrNull(param, "albumid") || ParameterIntOrNull(param, "genreid")))
-    return InvalidParams;
+  //if (!(ParameterIntOrNull(param, "artistid") || ParameterIntOrNull(param, "albumid") || ParameterIntOrNull(param, "genreid")))
+  //  return InvalidParams;
 
   CMusicDatabase musicdatabase;
   if (!musicdatabase.Open())
     return InternalError;
 
-  int artistID = ParameterAsInt(param, -1, "artistid");
-  int albumID  = ParameterAsInt(param, -1, "albumid");
-  int genreID  = ParameterAsInt(param, -1, "genreid");
+  int artistID = parameterObject.get("artistid", -1).asInt();
+  int albumID  = parameterObject.get("albumid", -1).asInt();
+  int genreID  = parameterObject.get("genreid", -1).asInt();
 
   CFileItemList items;
   if (musicdatabase.GetSongsNav("", items, genreID, artistID, albumID))
-    HandleFileItemList("songid", true, "songs", items, param, result);
+    HandleFileItemList("songid", true, "songs", items, parameterObject, result);
 
   musicdatabase.Close();
   return OK;
@@ -158,11 +154,10 @@ JSON_STATUS CAudioLibrary::GetSongDetails(const CStdString &method, ITransportLa
   if (!(parameterObject.isObject() || parameterObject.isNull()))
     return InvalidParams;
 
-  const Value param = ForceObject(parameterObject);
-  if (!(ParameterIntOrNull(param, "songid")))
-    return InvalidParams;
+  //if (!(ParameterIntOrNull(parameterObject, "songid")))
+  //  return InvalidParams;
 
-  int idSong = ParameterAsInt(param, -1, "songid");
+  int idSong = parameterObject.get("songid", -1).asInt();
   if (idSong <= 0)
     return InvalidParams;
 
@@ -190,15 +185,13 @@ JSON_STATUS CAudioLibrary::GetGenres(const CStdString &method, ITransportLayer *
   if (!(parameterObject.isObject() || parameterObject.isNull()))
     return InvalidParams;
 
-  const Value param = ForceObject(parameterObject);
-
   CMusicDatabase musicdatabase;
   if (!musicdatabase.Open())
     return InternalError;
 
   CFileItemList items;
   if (musicdatabase.GetGenresNav("", items))
-    HandleFileItemList("genreid", true, "genres", items, param, result);
+    HandleFileItemList("genreid", true, "genres", items, parameterObject, result);
 
   musicdatabase.Close();
   return OK;
@@ -219,15 +212,15 @@ bool CAudioLibrary::FillFileItemList(const Value &parameterObject, CFileItemList
   {
     if (parameterObject["artistid"].isInt() || parameterObject["albumid"].isInt() || parameterObject["genreid"].isInt())
     {
-      int artistID = ParameterAsInt(parameterObject, -1, "artistid");
-      int albumID  = ParameterAsInt(parameterObject, -1, "albumid");
-      int genreID  = ParameterAsInt(parameterObject, -1, "genreid");
+      int artistID = parameterObject.get("artistid", -1).asInt();
+      int albumID  = parameterObject.get("albumid", -1).asInt();
+      int genreID  = parameterObject.get("genreid", -1).asInt();
 
       success = musicdatabase.GetSongsNav("", list, genreID, artistID, albumID);
     }
     if (parameterObject["songid"].isInt())
     {
-      int songID = ParameterAsInt(parameterObject, -1, "songid");
+      int songID = parameterObject.get("songid", -1).asInt();
       if (songID != -1)
       {
         CSong song;
index 27b28c7..c8976fc 100644 (file)
@@ -158,22 +158,20 @@ void CFileItemHandler::HandleFileItem(const char *id, bool allowFile, const char
 
 bool CFileItemHandler::FillFileItemList(const Value &parameterObject, CFileItemList &list)
 {
-  Value param = ForceObject(parameterObject);
+  //if (parameterObject.isString())
+  //  parameterObject["file"] = parameterObject.asString();
 
-  if (parameterObject.isString())
-    param["file"] = parameterObject.asString();
-
-  if (param["file"].isString())
+  if (parameterObject["file"].isString())
   {
-    CStdString file = param["file"].asString();
+    CStdString file = parameterObject["file"].asString();
     CFileItemPtr item = CFileItemPtr(new CFileItem(file, URIUtils::HasSlashAtEnd(file)));
     list.Add(item);
   }
 
-  CPlaylistOperations::FillFileItemList(param, list);
-  CAudioLibrary::FillFileItemList(param, list);
-  CVideoLibrary::FillFileItemList(param, list);
-  CFileOperations::FillFileItemList(param, list);
+  CPlaylistOperations::FillFileItemList(parameterObject, list);
+  CAudioLibrary::FillFileItemList(parameterObject, list);
+  CVideoLibrary::FillFileItemList(parameterObject, list);
+  CFileOperations::FillFileItemList(parameterObject, list);
 
   return true;
 }
index 0f0139d..0455d75 100644 (file)
 #include "utils/log.h"
 #include "utils/Variant.h"
 #include <string.h>
+#include "ServiceDescription.h"
 
 using namespace ANNOUNCEMENT;
 using namespace JSONRPC;
 using namespace Json;
 using namespace std;
 
-Command CJSONRPC::m_commands[] = {
+bool CJSONRPC::m_initialized = false;
+
+JsonRpcMethodMap CJSONRPC::m_methodMaps[] = {
+// JSON-RPC
+  { "JSONRPC.Introspect",                           CJSONRPC::Introspect },
+  { "JSONRPC.Version",                              CJSONRPC::Version },
+  { "JSONRPC.Permission",                           CJSONRPC::Permission },
+  { "JSONRPC.Ping",                                 CJSONRPC::Ping },
+  { "JSONRPC.GetAnnouncementFlags",                 CJSONRPC::GetAnnouncementFlags },
+  { "JSONRPC.SetAnnouncementFlags",                 CJSONRPC::SetAnnouncementFlags },
+  { "JSONRPC.Announce",                             CJSONRPC::Announce },
+
+// Player
+//  { "Player.GetActivePlayers",                      CPlayerOperations::GetActivePlayers },
+
+// Music player
+//  { "AudioPlayer.State",                            CAVPlayerOperations::State },
+//  { "AudioPlayer.PlayPause",                        CAVPlayerOperations::PlayPause },
+//  { "AudioPlayer.Stop",                             CAVPlayerOperations::Stop },
+//  { "AudioPlayer.SkipPrevious",                     CAVPlayerOperations::SkipPrevious },
+//  { "AudioPlayer.SkipNext",                         CAVPlayerOperations::SkipNext },
+//
+//  { "AudioPlayer.BigSkipBackward",                  CAVPlayerOperations::BigSkipBackward },
+//  { "AudioPlayer.BigSkipForward",                   CAVPlayerOperations::BigSkipForward },
+//  { "AudioPlayer.SmallSkipBackward",                CAVPlayerOperations::SmallSkipBackward },
+//  { "AudioPlayer.SmallSkipForward",                 CAVPlayerOperations::SmallSkipForward },
+//
+//  { "AudioPlayer.Rewind",                           CAVPlayerOperations::Rewind },
+//  { "AudioPlayer.Forward",                          CAVPlayerOperations::Forward },
+//
+//  { "AudioPlayer.GetTime",                          CAVPlayerOperations::GetTime },
+//  { "AudioPlayer.GetTimeMS",                        CAVPlayerOperations::GetTimeMS },
+//  { "AudioPlayer.GetPercentage",                    CAVPlayerOperations::GetPercentage },
+//  { "AudioPlayer.SeekTime",                         CAVPlayerOperations::SeekTime },
+//  { "AudioPlayer.SeekPercentage",                   CAVPlayerOperations::SeekPercentage },
+//
+//  { "AudioPlayer.Record",                           CAVPlayerOperations::Record },
+
+// Video player
+//  { "VideoPlayer.State",                            CAVPlayerOperations::State },
+//  { "VideoPlayer.PlayPause",                        CAVPlayerOperations::PlayPause },
+//  { "VideoPlayer.Stop",                             CAVPlayerOperations::Stop },
+//  { "VideoPlayer.SkipPrevious",                     CAVPlayerOperations::SkipPrevious },
+//  { "VideoPlayer.SkipNext",                         CAVPlayerOperations::SkipNext },
+//
+//  { "VideoPlayer.BigSkipBackward",                  CAVPlayerOperations::BigSkipBackward },
+//  { "VideoPlayer.BigSkipForward",                   CAVPlayerOperations::BigSkipForward },
+//  { "VideoPlayer.SmallSkipBackward",                CAVPlayerOperations::SmallSkipBackward },
+//  { "VideoPlayer.SmallSkipForward",                 CAVPlayerOperations::SmallSkipForward },
+//
+//  { "VideoPlayer.Rewind",                           CAVPlayerOperations::Rewind },
+//  { "VideoPlayer.Forward",                          CAVPlayerOperations::Forward },
+//
+//  { "VideoPlayer.GetTime",                          CAVPlayerOperations::GetTime },
+//  { "VideoPlayer.GetTimeMS",                        CAVPlayerOperations::GetTimeMS },
+//  { "VideoPlayer.GetPercentage",                    CAVPlayerOperations::GetPercentage },
+//  { "VideoPlayer.SeekTime",                         CAVPlayerOperations::SeekTime },
+//  { "VideoPlayer.SeekPercentage",                   CAVPlayerOperations::SeekPercentage },
+
+// Picture player
+//  { "PicturePlayer.PlayPause",                      CPicturePlayerOperations::PlayPause },
+//  { "PicturePlayer.Stop",                           CPicturePlayerOperations::Stop },
+//  { "PicturePlayer.SkipPrevious",                   CPicturePlayerOperations::SkipPrevious },
+//  { "PicturePlayer.SkipNext",                       CPicturePlayerOperations::SkipNext },
+//
+//  { "PicturePlayer.MoveLeft",                       CPicturePlayerOperations::MoveLeft },
+//  { "PicturePlayer.MoveRight",                      CPicturePlayerOperations::MoveRight },
+//  { "PicturePlayer.MoveDown",                       CPicturePlayerOperations::MoveDown },
+//  { "PicturePlayer.MoveUp",                         CPicturePlayerOperations::MoveUp },
+//
+//  { "PicturePlayer.ZoomOut",                        CPicturePlayerOperations::ZoomOut },
+//  { "PicturePlayer.ZoomIn",                         CPicturePlayerOperations::ZoomIn },
+//  { "PicturePlayer.Zoom",                           CPicturePlayerOperations::Zoom },
+//  { "PicturePlayer.Rotate",                         CPicturePlayerOperations::Rotate },
+
+// Video Playlist
+  // TODO
+
+// Audio Playlist
+  // TODO
+
+// Playlist
+//  { "Playlist.Create",                              CPlaylistOperations::Create },
+//  { "Playlist.Destroy",                             CPlaylistOperations::Destroy },
+//
+//  { "Playlist.GetItems",                            CPlaylistOperations::GetItems },
+//  { "Playlist.Add",                                 CPlaylistOperations::Add },
+//  { "Playlist.Remove",                              CPlaylistOperations::Remove },
+//  { "Playlist.Swap",                                CPlaylistOperations::Swap },
+//  { "Playlist.Shuffle",                             CPlaylistOperations::Shuffle },
+//  { "Playlist.UnShuffle",                           CPlaylistOperations::UnShuffle },
+
+// Files
+//  { "Files.GetSources",                             CFileOperations::GetRootDirectory },
+//  { "Files.Download",                               CFileOperations::Download },
+//  { "Files.GetDirectory",                           CFileOperations::GetDirectory },
+
+// Music Library
+  // TODO
+
+// Video Library
+  // TODO
+
+// System operations
+//  { "System.Shutdown",                              CSystemOperations::Shutdown },
+//  { "System.Suspend",                               CSystemOperations::Suspend },
+//  { "System.Hibernate",                             CSystemOperations::Hibernate },
+//  { "System.Reboot",                                CSystemOperations::Reboot },
+//  { "System.GetInfoLabels",                         CSystemOperations::GetInfoLabels },
+//  { "System.GetInfoBooleans",                       CSystemOperations::GetInfoBooleans },
+
+// XBMC operations
+//  { "XBMC.GetVolume",                               CXBMCOperations::GetVolume },
+//  { "XBMC.SetVolume",                               CXBMCOperations::SetVolume },
+//  { "XBMC.ToggleMute",                              CXBMCOperations::ToggleMute },
+//  { "XBMC.Play",                                    CXBMCOperations::Play },
+//  { "XBMC.StartSlideshow",                          CXBMCOperations::StartSlideshow },
+//  { "XBMC.Log",                                     CXBMCOperations::Log },
+//  { "XBMC.Quit",                                    CXBMCOperations::Quit }
+};
+
+/*Command CJSONRPC::m_commands[] = {
 // JSON-RPC
   { "JSONRPC.Introspect",                           CJSONRPC::Introspect,                                Response,     ReadData,        "Enumerates all actions and descriptions. Parameter example {\"getdescriptions\": true, \"getpermissions\": true, \"filterbytransport\": true }. All parameters optional" },
   { "JSONRPC.Version",                              CJSONRPC::Version,                                   Response,     ReadData,        "Retrieve the jsonrpc protocol version" },
@@ -205,45 +327,36 @@ Command CJSONRPC::m_commands[] = {
   { "XBMC.Log",                                     CXBMCOperations::Log,                                Response,     Logging,         "Logs a line in the xbmc.log. Parameter example {\"message\": \"foo\", \"level\": \"info\"} or just a string to log message with level debug" },
 
   { "XBMC.Quit",                                    CXBMCOperations::Quit,                               Response,     ControlPower,    "Quit xbmc" }
-};
+};*/
 
-CJSONRPC::CActionMap CJSONRPC::m_actionMap(m_commands, sizeof(m_commands) / sizeof(m_commands[0]) );
-
-JSON_STATUS CJSONRPC::Introspect(const CStdString &method, ITransportLayer *transport, IClient *client, const Json::Value& parameterObject, Json::Value &result)
+void CJSONRPC::Initialize()
 {
-  if (!(parameterObject.isObject() || parameterObject.isNull()))
-    return InvalidParams;
-
-  const Value param = parameterObject.isObject() ? parameterObject : Value(objectValue);
-  bool getDescriptions = param.get("getdescriptions", true).asBool();
-  bool getPermissions = param.get("getpermissions", true).asBool();
-  bool filterByTransport = param.get("filterbytransport", true).asBool();
-
-  int length = sizeof(m_commands) / sizeof(Command);
-  int clientflags = client->GetPermissionFlags();
-  for (int i = 0; i < length; i++)
-  {
-    if ((transport->GetCapabilities() & m_commands[i].transportneed) == 0 && filterByTransport)
-      continue;
+  if (m_initialized)
+    return;
 
-    Value val;
+  unsigned int size = sizeof(m_methodMaps) / sizeof(JsonRpcMethodMap);
+  if (!CJSONServiceDescription::Parse(m_methodMaps, size))
+    CLog::Log(LOGSEVERE, "JSONRPC: Error while parsing the json rpc service description");
+  else
+    CLog::Log(LOGINFO, "JSONRPC: json rpc service description successfully loaded");
+  
+  m_initialized = true;
+}
 
-    val["command"] = m_commands[i].command;
-    val["executable"] = (clientflags & m_commands[i].permission) > 0 ? true : false;
-    if (getDescriptions && m_commands[i].description)
-      val["description"] = m_commands[i].description;
-    if (getPermissions)
-      val["permission"] = PermissionToString(m_commands[i].permission);
+JSON_STATUS CJSONRPC::Introspect(const CStdString &method, ITransportLayer *transport, IClient *client, const Json::Value& parameterObject, Json::Value &result)
+{
+  bool getDescriptions = parameterObject["getdescriptions"].asBool();
+  bool getMetadata = parameterObject["getmetadata"].asBool();
+  bool filterByTransport = parameterObject["filterbytransport"].asBool();
 
-    result["commands"].append(val);
-  }
+  CJSONServiceDescription::Print(result, transport, client, getDescriptions, getMetadata, filterByTransport);
 
   return OK;
 }
 
 JSON_STATUS CJSONRPC::Version(const CStdString &method, ITransportLayer *transport, IClient *client, const Json::Value& parameterObject, Json::Value &result)
 {
-  result["version"] = 3;
+  result["version"] = CJSONServiceDescription::GetVersion();
 
   return OK;
 }
@@ -277,20 +390,17 @@ JSON_STATUS CJSONRPC::GetAnnouncementFlags(const CStdString &method, ITransportL
 
 JSON_STATUS CJSONRPC::SetAnnouncementFlags(const CStdString &method, ITransportLayer *transport, IClient *client, const Json::Value& parameterObject, Json::Value &result)
 {
-  if (!parameterObject.isObject())
-    return InvalidParams;
-
   int flags = 0;
 
-  if (parameterObject.get("Playback", false).asBool())
+  if (parameterObject["Playback"].asBool())
     flags |= Playback;
-  if (parameterObject.get("GUI", false).asBool())
+  if (parameterObject["GUI"].asBool())
     flags |= GUI;
-  if (parameterObject.get("System", false).asBool())
+  if (parameterObject["System"].asBool())
     flags |= System;
-  if (parameterObject.get("Library", false).asBool())
+  if (parameterObject["Library"].asBool())
     flags |= Library;
-  if (parameterObject.get("Other", false).asBool())
+  if (parameterObject["Other"].asBool())
     flags |= Other;
 
   if (client->SetAnnouncementFlags(flags))
@@ -301,15 +411,14 @@ JSON_STATUS CJSONRPC::SetAnnouncementFlags(const CStdString &method, ITransportL
 
 JSON_STATUS CJSONRPC::Announce(const CStdString &method, ITransportLayer *transport, IClient *client, const Json::Value& parameterObject, Json::Value &result)
 {
-  if (!parameterObject.isObject() || !parameterObject.isMember("sender") || !parameterObject.isMember("message"))
-    return InvalidParams;
-
-  if (!parameterObject.isMember("data"))
-    CAnnouncementManager::Announce(Other, parameterObject["sender"].asString().c_str(), parameterObject["message"].asString().c_str());
+  if (parameterObject["data"].isNull())
+    CAnnouncementManager::Announce(Other, parameterObject["sender"].asString().c_str(),  
+      parameterObject["message"].asString().c_str());
   else
   {
     CVariant data(parameterObject["data"].asString());
-    CAnnouncementManager::Announce(Other, parameterObject["sender"].asString().c_str(), parameterObject["message"].asString().c_str(), data);
+    CAnnouncementManager::Announce(Other, parameterObject["sender"].asString().c_str(),  
+      parameterObject["message"].asString().c_str(), data);
   }
 
   return ACK;
@@ -381,9 +490,16 @@ bool CJSONRPC::HandleMethodCall(Value& request, Value& response, ITransportLayer
   {
     isAnnouncement = !request.isMember("id");
 
-    CStdString method = request.get("method", "").asString();
-    method = method.ToLower();
-    errorCode = InternalMethodCall(method, request, result, transport, client);
+    CStdString methodName = request.get("method", "").asString();
+    methodName = methodName.ToLower();
+
+    JSONRPC::MethodCall method;
+    Json::Value params;
+
+    if ((errorCode = CJSONServiceDescription::CheckCall(methodName, request["params"], client, isAnnouncement, method, params)) == OK)
+      errorCode = method(methodName, transport, client, params, result);
+    else
+      result = params;
   }
   else
   {
@@ -397,23 +513,9 @@ bool CJSONRPC::HandleMethodCall(Value& request, Value& response, ITransportLayer
   return !isAnnouncement;
 }
 
-JSON_STATUS CJSONRPC::InternalMethodCall(const CStdString& method, Value& o, Value &result, ITransportLayer *transport, IClient *client)
-{
-  CActionMap::const_iterator iter = m_actionMap.find(method);
-  if( iter != m_actionMap.end() )
-  {
-    if (client->GetPermissionFlags() & iter->second.permission)
-      return iter->second.method(method, transport, client, o["params"], result);
-    else
-      return BadPermission;
-  }
-  else
-    return MethodNotFound;
-}
-
 inline bool CJSONRPC::IsProperJSONRPC(const Json::Value& inputroot)
 {
-  return inputroot.isObject() && inputroot.isMember("jsonrpc") && inputroot["jsonrpc"].isString() && inputroot.get("jsonrpc", "-1").asString() == "2.0" && inputroot.isMember("method") && inputroot["method"].isString();
+  return inputroot.isObject() && inputroot.isMember("jsonrpc") && inputroot["jsonrpc"].isString() && inputroot.get("jsonrpc", "-1").asString() == "2.0" && inputroot.isMember("method") && inputroot["method"].isString() && (!inputroot.isMember("params") || inputroot["params"].isArray() || inputroot["params"].isObject());
 }
 
 inline void CJSONRPC::BuildResponse(const Value& request, JSON_STATUS code, const Value& result, Value& response)
@@ -436,6 +538,8 @@ inline void CJSONRPC::BuildResponse(const Value& request, JSON_STATUS code, cons
     case InvalidParams:
       response["error"]["code"] = InvalidParams;
       response["error"]["message"] = "Invalid params.";
+      if (!result.isNull())
+        response["error"]["data"] = result;
       break;
     case MethodNotFound:
       response["error"]["code"] = MethodNotFound;
@@ -459,63 +563,3 @@ inline void CJSONRPC::BuildResponse(const Value& request, JSON_STATUS code, cons
       break;
   }
 }
-
-inline const char *CJSONRPC::PermissionToString(const OperationPermission &permission)
-{
-  switch (permission)
-  {
-  case ReadData:
-    return "ReadData";
-  case ControlPlayback:
-    return "ControlPlayback";
-  case ControlAnnounce:
-    return "ControlAnnounce";
-  case ControlPower:
-    return "ControlPower";
-  case Logging:
-    return "Logging";
-  case ScanLibrary:
-    return "ScanLibrary";
-  default:
-    return "Unknown";
-  }
-}
-
-inline const char *CJSONRPC::AnnouncementFlagToString(const EAnnouncementFlag &announcement)
-{
-  switch (announcement)
-  {
-  case Playback:
-    return "Playback";
-  case GUI:
-    return "GUI";
-  case System:
-    return "System";
-  case Library:
-    return "Library";
-  case Other:
-    return "Other";
-  default:
-    return "Unknown";
-  }
-}
-
-CJSONRPC::CActionMap::CActionMap(const Command commands[], int length)
-{
-  for (int i = 0; i < length; i++)
-  {
-    CStdString command = commands[i].command;
-    command = command.ToLower();
-    m_actionmap[command] = commands[i];
-  }
-}
-
-CJSONRPC::CActionMap::const_iterator CJSONRPC::CActionMap::find(const CStdString& key) const
-{
-  return m_actionmap.find(key);
-}
-
-CJSONRPC::CActionMap::const_iterator CJSONRPC::CActionMap::end() const
-{
-  return m_actionmap.end();
-}
index fd2c34f..4230ec2 100644 (file)
 #include "ITransportLayer.h"
 #include "interfaces/IAnnouncer.h"
 #include "jsoncpp/include/json/json.h"
+#include "JSONUtils.h"
+#include "JSONServiceDescription.h"
 
 namespace JSONRPC
 {
-  enum JSON_STATUS
-  {
-    OK = 0,
-    ACK = -1,
-    InvalidRequest = -32600,
-    MethodNotFound = -32601,
-    InvalidParams = -32602,
-    InternalError = -32603,
-    ParseError = -32700,
-  //-32099..-32000 Reserved for implementation-defined server-errors.
-    BadPermission = -32099,
-    FailedToExecute = -32100
-  };
-
-  /* The method call needs to be perfectly threadsafe
-     The method will only be called if the caller has the correct permissions. The method will need to check parameters for bad parametervalues.
-  */
-  typedef JSON_STATUS (*MethodCall) (const CStdString &method, ITransportLayer *transport, IClient *client, const Json::Value& parameterObject, Json::Value &result);
+  /*!
+   \ingroup jsonrpc
+   \brief JSON RPC handler
 
-  enum OperationPermission
+   Sets up and manages all needed information to process
+   JSON RPC requests and answering with the appropriate
+   JSON RPC response (actual response or error message).
+   */
+  class CJSONRPC : public CJSONUtils
   {
-    ReadData = 0x1,
-    ControlPlayback = 0x2,
-    ControlAnnounce = 0x4,
-    ControlPower = 0x8,
-    Logging = 0x10,
-    ScanLibrary = 0x20,
-  };
+  public:
+    /*!
+     \brief Initializes the JSON RPC handler
+     */
+    static void Initialize();
 
-  static const int OPERATION_PERMISSION_ALL = (ReadData | ControlPlayback | ControlAnnounce | ControlPower | Logging | ScanLibrary);
+    /*
+     \brief Handles an incoming JSON RPC request
+     \param inputString received JSON RPC request
+     \param transport Transport protocol on which the request arrived
+     \param client Client which sent the request
+     \return JSON RPC response to be sent back to the client
 
-  typedef struct
-  {
-    const char* command;
-    MethodCall method;
-    TransportLayerCapability transportneed;
-    OperationPermission permission;
-    const char* description;
-  } Command;
-
-  class CJSONRPC
-  {
-  public:
+     Parses the received input string for the called method and provided
+     parameters. If the request does not conform to the JSON RPC 2.0
+     specification an error is returned. Otherwise the parameters provided
+     in the request are checked for validity and completeness. If the request
+     is valid and the requested method exists it is called and executed.
+     */
     static CStdString MethodCall(const CStdString &inputString, ITransportLayer *transport, IClient *client);
 
     static JSON_STATUS Introspect(const CStdString &method, ITransportLayer *transport, IClient *client, const Json::Value& parameterObject, Json::Value &result);
@@ -83,28 +71,15 @@ namespace JSONRPC
     static JSON_STATUS GetAnnouncementFlags(const CStdString &method, ITransportLayer *transport, IClient *client, const Json::Value& parameterObject, Json::Value &result);
     static JSON_STATUS SetAnnouncementFlags(const CStdString &method, ITransportLayer *transport, IClient *client, const Json::Value& parameterObject, Json::Value &result);
     static JSON_STATUS Announce(const CStdString &method, ITransportLayer *transport, IClient *client, const Json::Value& parameterObject, Json::Value &result);
+  
   private:
+    static void setup();
     static bool HandleMethodCall(Json::Value& request, Json::Value& response, ITransportLayer *transport, IClient *client);
-    static JSON_STATUS InternalMethodCall(const CStdString& method, Json::Value& o, Json::Value &result, ITransportLayer *transport, IClient *client);
     static inline bool IsProperJSONRPC(const Json::Value& inputroot);
 
     inline static void BuildResponse(const Json::Value& request, JSON_STATUS code, const Json::Value& result, Json::Value& response);
-    inline static const char *PermissionToString(const OperationPermission &permission);
-    inline static const char *AnnouncementFlagToString(const ANNOUNCEMENT::EAnnouncementFlag &announcement);
-
-    class CActionMap
-    {
-    public:
-      CActionMap(const Command commands[], int length);
-
-      typedef std::map<CStdString, Command>::const_iterator const_iterator;
-      const_iterator find(const CStdString& key) const;
-      const_iterator end() const;
-    private:
-      std::map<CStdString, Command> m_actionmap;
-    };
 
-    static Command    m_commands[];
-    static CActionMap m_actionMap;
+    static JsonRpcMethodMap m_methodMaps[];
+    static bool m_initialized;
   };
 }
diff --git a/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp b/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp
new file mode 100644 (file)
index 0000000..98a1b5f
--- /dev/null
@@ -0,0 +1,1121 @@
+/*
+ *      Copyright (C) 2005-2010 Team XBMC
+ *      http://www.xbmc.org
+ *
+ *  This Program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This Program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with XBMC; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *  http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+#include <limits>
+#include "ServiceDescription.h"
+#include "JSONServiceDescription.h"
+#include "utils/log.h"
+#include "utils/StringUtils.h"
+
+using namespace std;
+using namespace JSONRPC;
+
+JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::CJsonSchemaPropertiesMap()
+{
+  m_propertiesmap = std::map<CStdString, JSONSchemaTypeDefinition>();
+}
+
+void JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::add(JSONSchemaTypeDefinition &property)
+{
+  CStdString name = property.name;
+  name = name.ToLower();
+  m_propertiesmap[name] = property;
+}
+
+JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::begin() const
+{
+  return m_propertiesmap.begin();
+}
+
+JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::find(const CStdString& key) const
+{
+  return m_propertiesmap.find(key);
+}
+
+JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::end() const
+{
+  return m_propertiesmap.end();
+}
+
+unsigned int JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::size() const
+{
+  return m_propertiesmap.size();
+}
+
+Json::Value CJSONServiceDescription::m_notifications = Json::Value(Json::objectValue);
+CJSONServiceDescription::CJsonRpcMethodMap CJSONServiceDescription::m_actionMap;
+JsonRpcDescriptionHeader CJSONServiceDescription::m_header;
+std::map<CStdString, JSONSchemaTypeDefinition> CJSONServiceDescription::m_types = std::map<CStdString, JSONSchemaTypeDefinition>();
+std::vector<CStdString> CJSONServiceDescription::m_badMethods = std::vector<CStdString>();
+bool CJSONServiceDescription::m_newReferenceType = false;
+
+bool CJSONServiceDescription::Parse(const JsonRpcMethodMap methodMap[], unsigned int size)
+{
+  Json::Value descriptionObject;
+  Json::Reader reader;
+
+  // Read the json schema for notifications
+  if (!reader.parse(JSON_NOTIFICATION_DESCRIPTION, m_notifications))
+    CLog::Log(LOGERROR, "JSONRPC: Unable to read the json schema notification description");
+
+  // Read the json schema service descriptor and check if it represents
+  // a json object and contains a "services" element for methods
+  if (!reader.parse(JSON_SERVICE_DESCRIPTION, descriptionObject))
+  {
+    CLog::Log(LOGERROR, "JSONRPC: Unable to read the json schema service description");
+    return false;
+  }
+
+  // First parse the header
+  parseHeader(descriptionObject);
+  
+  // Get all child elements
+  Json::Value::Members methodNames = descriptionObject.getMemberNames();
+
+  // Loop through the methods
+  for (unsigned int index = 0; index < methodNames.size(); index++) 
+  {
+    CStdString methodName = methodNames.at(index);
+    // Make sure the method actually exists and represents an object
+    if (!descriptionObject.isMember(methodName) || !descriptionObject[methodName].isObject() || 
+        !descriptionObject[methodName].isMember("type") || !descriptionObject[methodName]["type"].isString())
+      continue;
+
+    CStdString type = GetString(descriptionObject[methodName]["type"], "");
+    if (type.compare("method") == 0)
+    {
+      // Check if the method is available in the method map
+      JSONRPC::MethodCall methodCall = NULL;
+      unsigned int mapIndex;
+      for (mapIndex = 0; mapIndex < size; mapIndex++)
+      {
+        if (methodName.compare(methodMap[mapIndex].name) == 0)
+        {
+          methodCall = methodMap[mapIndex].method;
+          break;
+        }
+      }
+
+      // If the method is not available in the method map
+      // we have to ignore it
+      if (methodCall == NULL)
+      {
+        CLog::Log(LOGERROR, "JSONRPC: No implementation for method %s found", methodName.c_str());
+        continue;
+      }
+
+      // Parse the details of the method
+      JsonRpcMethod method;
+      method.name = methodMap[mapIndex].name;
+      method.method = methodCall;
+      if (!parseMethod(descriptionObject[methodName], method))
+      {
+        // If parsing failed add the method to the list of currently bad methods
+        // (might be that a reference for a parameter is missing)
+        m_badMethods.push_back(methodName);
+        CLog::Log(LOGDEBUG, "JSONRPC: Method %s could not be parsed correctly and might be re-parsed later", methodName.c_str());
+        continue;
+      }
+
+      m_actionMap.add(method);
+    }
+    else if (descriptionObject[methodName].isMember("id") && descriptionObject[methodName]["id"].isString())
+    {
+      JSONSchemaTypeDefinition globalType;
+      globalType.name = methodName.c_str();
+      parseTypeDefinition(descriptionObject[methodName], globalType, false);
+    }
+  }
+
+  // As long as there have been new reference types
+  // and there are more bad methods than in the last
+  // try we can try parsing again
+  unsigned int badMethodCount = m_badMethods.size() + 1;
+  while (m_newReferenceType && m_badMethods.size() > 0 && m_badMethods.size() < badMethodCount)
+  {
+    m_newReferenceType = false;
+    badMethodCount = m_badMethods.size();
+    std::vector<CStdString> stillBadMethods = std::vector<CStdString>();
+
+    for (unsigned int methodIndex = 0; methodIndex < badMethodCount; methodIndex++)
+    {
+      JsonRpcMethod method;
+      // Check if the method is available in the method map
+      JSONRPC::MethodCall methodCall = NULL;
+      unsigned int mapIndex;
+      for (mapIndex = 0; mapIndex < size; mapIndex++)
+      {
+        if (m_badMethods.at(methodIndex).compare(methodMap[mapIndex].name) == 0)
+        {
+          methodCall = methodMap[mapIndex].method;
+          break;
+        }
+      }
+
+      // If the method is not available in the method map
+      // we have to ignore it
+      if (methodCall == NULL)
+      {
+        CLog::Log(LOGERROR, "JSONRPC: No implementation for method %s found", m_badMethods.at(methodIndex).c_str());
+        continue;
+      }
+
+      // Parse the details of the method
+      method.name = methodMap[mapIndex].name;
+      method.method = methodCall;
+      if (!parseMethod(descriptionObject[m_badMethods.at(methodIndex)], method))
+      {
+        // If parsing still failed add the method to the list of currently bad methods
+        // (might be that a reference for a parameter is missing)
+        stillBadMethods.push_back(m_badMethods.at(methodIndex));
+        CLog::Log(LOGDEBUG, "JSONRPC: Method %s could not be parsed correctly and might be re-parsed later", m_badMethods.at(methodIndex).c_str());
+        continue;
+      }
+
+      m_actionMap.add(method);
+    }
+
+    m_badMethods = stillBadMethods;
+  }
+
+  // Print a log message for every unparseable method
+  for (unsigned int badMethodIndex = 0; badMethodIndex < m_badMethods.size(); badMethodIndex++)
+    CLog::Log(LOGERROR, "JSONRPC: Method %s could not be parsed correctly and will be ignored", m_badMethods.at(badMethodIndex).c_str());
+
+  return true;
+}
+
+int CJSONServiceDescription::GetVersion()
+{
+  return m_header.version;
+}
+
+void CJSONServiceDescription::Print(Json::Value &result, ITransportLayer *transport, IClient *client, bool printDescriptions, bool printMetadata, bool filterByTransport)
+{
+  // Print the header
+  result["id"] = m_header.id;
+  result["version"] = m_header.version;
+  result["description"] = m_header.description;
+
+  std::map<CStdString, JSONSchemaTypeDefinition>::const_iterator typeIterator;
+  std::map<CStdString, JSONSchemaTypeDefinition>::const_iterator typeIteratorEnd = m_types.end();
+  for (typeIterator = m_types.begin(); typeIterator != typeIteratorEnd; typeIterator++)
+  {
+    Json::Value currentType = Json::Value(Json::objectValue);
+    printType(typeIterator->second, false, true, true, printDescriptions, currentType);
+
+    result["types"][typeIterator->first] = currentType;
+  }
+
+  // Iterate through all json rpc methods
+  int clientPermissions = client->GetPermissionFlags();
+  int transportCapabilities = transport->GetCapabilities();
+
+  CJsonRpcMethodMap::JsonRpcMethodIterator methodIterator;
+  CJsonRpcMethodMap::JsonRpcMethodIterator methodIteratorEnd = m_actionMap.end();
+  for (methodIterator = m_actionMap.begin(); methodIterator != methodIteratorEnd; methodIterator++)
+  {
+    if ((clientPermissions & methodIterator->second.permission) == 0 || ((transportCapabilities & methodIterator->second.transportneed) == 0 && filterByTransport))
+      continue;
+
+    Json::Value currentMethod = Json::Value(Json::objectValue);
+
+    currentMethod["type"] = "method";
+    if (printDescriptions && strlen(methodIterator->second.description) > 0)
+      currentMethod["description"] = methodIterator->second.description;
+    if (printMetadata)
+    {
+      currentMethod["permission"] = PermissionToString(methodIterator->second.permission);
+           currentMethod["statechanging"] = methodIterator->second.stateChanging;
+    }
+
+    currentMethod["params"] = Json::Value(Json::arrayValue);
+    for (unsigned int paramIndex = 0; paramIndex < methodIterator->second.parameters.size(); paramIndex++)
+    {
+      Json::Value param = Json::Value(Json::objectValue);
+      printType(methodIterator->second.parameters.at(paramIndex), true, false, true, printDescriptions, param);
+      currentMethod["params"].append(param);
+    }
+
+    currentMethod["returns"] = methodIterator->second.returns;
+
+    result["methods"][methodIterator->second.name] = currentMethod;
+  }
+
+  // Print notification description
+  Json::Value::Members notifications = m_notifications.getMemberNames();
+  for (unsigned int notifIndex = 0; notifIndex < notifications.size(); notifIndex++)
+  {
+    if (!m_notifications.isMember(notifications.at(notifIndex)) ||
+        !m_notifications[notifications.at(notifIndex)].isObject() ||
+        !m_notifications[notifications.at(notifIndex)].isMember("type") ||
+        !m_notifications[notifications.at(notifIndex)]["type"].isString() ||
+         m_notifications[notifications.at(notifIndex)]["type"].asString().compare("notification") != 0)
+    {
+      continue;
+    }
+
+    result["notifications"][notifications.at(notifIndex)] = m_notifications[notifications.at(notifIndex)];
+  }
+}
+
+JSON_STATUS CJSONServiceDescription::CheckCall(const char* const method, const Json::Value &requestParameters, IClient *client, bool announcement, MethodCall &methodCall, Json::Value &outputParameters)
+{
+  CJsonRpcMethodMap::JsonRpcMethodIterator iter = m_actionMap.find(method);
+  if (iter != m_actionMap.end())
+  {
+    if (client != NULL && (client->GetPermissionFlags() & iter->second.permission) && (!announcement || iter->second.stateChanging))
+    {
+      methodCall = iter->second.method;
+
+      // Count the number of actually handled (present)
+      // parameters
+      unsigned int handled = 0;
+      Json::Value errorData = Json::Value(Json::objectValue);
+      errorData["method"] = iter->second.name;
+
+      // Loop through all the parameters to check
+      for (unsigned int i = 0; i < iter->second.parameters.size(); i++)
+      {
+        // Evaluate the current parameter
+        JSON_STATUS status = checkParameter(requestParameters, iter->second.parameters.at(i), i, outputParameters, handled, errorData);
+        if (status != OK)
+        {
+          // Return the error data object in the outputParameters reference
+          outputParameters = errorData;
+          return status;
+        }
+      }
+
+      // Check if there were unnecessary parameters
+      if (handled < requestParameters.size())
+      {
+        errorData["message"] = "Too many parameters";
+        outputParameters = errorData;
+        return InvalidParams;
+      }
+
+      return OK;
+    }
+    else
+      return BadPermission;
+  }
+  else
+    return MethodNotFound;
+}
+
+void CJSONServiceDescription::printType(const JSONSchemaTypeDefinition &type, bool isParameter, bool isGlobal, bool printDefault, bool printDescriptions, Json::Value &output)
+{
+  bool typeReference = false;
+
+  // Printing general fields
+  if (isParameter)
+    output["name"] = type.name;
+
+  if (isGlobal)
+    output["id"] = type.id;
+  else if (type.id != NULL && strlen(type.id) > 0)
+  {
+    output["$ref"] = type.id;
+    typeReference = true;
+  }
+
+  if (printDescriptions && strlen(type.description) > 0)
+    output["description"] = type.description;
+
+  if (isParameter || printDefault)
+  {
+    if (!type.optional)
+      output["required"] = true;
+    if (type.optional && type.type != ObjectValue && type.type != ArrayValue)
+      output["default"] = type.defaultValue;
+  }
+
+  if (!typeReference)
+  {
+    SchemaValueTypeToJson(type.type, output["type"]);
+
+    // Printing enum field
+    if (type.enums.size() > 0)
+    {
+      output["enums"] = Json::Value(Json::arrayValue);
+      for (unsigned int enumIndex = 0; enumIndex < type.enums.size(); enumIndex++)
+        output["enums"].append(type.enums.at(enumIndex));
+    }
+
+    // Printing integer/number fields
+    if (HasType(type.type, IntegerValue) || HasType(type.type, NumberValue))
+    {
+      if (HasType(type.type, NumberValue))
+      {
+        if (type.minimum > -numeric_limits<double>::max())
+          output["minimum"] = type.minimum;
+        if (type.maximum < numeric_limits<double>::max())
+          output["maximum"] = type.maximum;
+      }
+      else
+      {
+        if (type.minimum > numeric_limits<int>::min())
+          output["minimum"] = (int)type.minimum;
+        if (type.maximum < numeric_limits<int>::max())
+          output["maximum"] = (int)type.maximum;
+      }
+
+      if (type.exclusiveMinimum)
+        output["exclusiveMinimum"] = true;
+      if (type.exclusiveMaximum)
+        output["exclusiveMaximum"] = true;
+      if (type.divisibleBy > 0)
+        output["divisibleBy"] = type.divisibleBy;
+    }
+
+    // Print array fields
+    if (HasType(type.type, ArrayValue))
+    {
+      if (type.items.size() == 1)
+      {
+        printType(type.items.at(0), false, false, false, printDescriptions, output["items"]);
+      }
+      else if (type.items.size() > 1)
+      {
+        output["items"] = Json::Value(Json::arrayValue);
+        for (unsigned int itemIndex = 0; itemIndex < type.items.size(); itemIndex++)
+        {
+          Json::Value item = Json::Value(Json::objectValue);
+          printType(type.items.at(itemIndex), false, false, false, printDescriptions, item);
+          output["items"].append(item);
+        }
+      }
+
+      if (type.minItems > 0)
+        output["minItems"] = type.minItems;
+      if (type.maxItems > 0)
+        output["maxItems"] = type.maxItems;
+
+      if (type.additionalItems.size() == 1)
+      {
+        printType(type.additionalItems.at(0), false, false, false, printDescriptions, output["additionalItems"]);
+      }
+      else if (type.additionalItems.size() > 1)
+      {
+        output["additionalItems"] = Json::Value(Json::arrayValue);
+        for (unsigned int addItemIndex = 0; addItemIndex < type.additionalItems.size(); addItemIndex++)
+        {
+          Json::Value item = Json::Value(Json::objectValue);
+          printType(type.additionalItems.at(addItemIndex), false, false, false, printDescriptions, item);
+          output["additionalItems"].append(item);
+        }
+      }
+
+      if (type.uniqueItems)
+        output["uniqueItems"] = true;
+    }
+
+    // Print object fields
+    if (HasType(type.type, ObjectValue) && type.properties.size() > 0)
+    {
+      output["properties"] = Json::Value(Json::objectValue);
+
+      JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesEnd = type.properties.end();
+      JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesIterator;
+      for (propertiesIterator = type.properties.begin(); propertiesIterator != propertiesEnd; propertiesIterator++)
+      {
+        printType(propertiesIterator->second, false, false, true, printDescriptions, output["properties"][propertiesIterator->first]);
+      }
+    }
+  }
+}
+
+JSON_STATUS CJSONServiceDescription::checkParameter(const Json::Value &requestParameters, const JSONSchemaTypeDefinition &type, unsigned int position, Json::Value &outputParameters, unsigned int &handled, Json::Value &errorData)
+{
+  // Let's check if the parameter has been provided
+  if (ParameterExists(requestParameters, type.name, position))
+  {
+    // Get the parameter
+    Json::Value parameterValue = GetParameter(requestParameters, type.name, position);
+
+    // Evaluate the type of the parameter
+    JSON_STATUS status = checkType(parameterValue, type, outputParameters[type.name], errorData["stack"]);
+    if (status != OK)
+      return status;
+
+    // The parameter was present and valid
+    handled++;
+  }
+  // If the parameter has not been provided but is optional
+  // we can use its default value
+  else if (type.optional)
+    outputParameters[type.name] = type.defaultValue;
+  // The parameter is required but has not been provided => invalid
+  else
+  {
+    errorData["stack"]["name"] = type.name;
+    SchemaValueTypeToJson(type.type, errorData["stack"]["type"]);
+    errorData["stack"]["message"] = "Missing parameter";
+    return InvalidParams;
+  }
+
+  return OK;
+}
+
+JSON_STATUS CJSONServiceDescription::checkType(const Json::Value &value, const JSONSchemaTypeDefinition &type, Json::Value &outputValue, Json::Value &errorData)
+{
+  if (type.name != NULL)
+    errorData["name"] = type.name;
+  SchemaValueTypeToJson(type.type, errorData["type"]);
+  CStdString errorMessage;
+
+  // Let's check the type of the provided parameter
+  if (!IsType(value, type.type))
+  {
+    CLog::Log(LOGWARNING, "JSONRPC: Type mismatch in type %s", type.name);
+    errorMessage.Format("Invalid type %s received", ValueTypeToString(value.type()));
+    errorData["message"] = errorMessage.c_str();
+    return InvalidParams;
+  }
+  else if (value.isNull() && !HasType(type.type, NullValue))
+  {
+    CLog::Log(LOGWARNING, "JSONRPC: Value is NULL in type %s", type.name);
+    errorData["message"] = "Received value is null";
+    return InvalidParams;
+  }
+
+  // If it is an array we need to
+  // - check the type of every element ("items")
+  // - check if they need to be unique ("uniqueItems")
+  if (HasType(type.type, ArrayValue) && value.isArray())
+  {
+    // Check the number of items against minItems and maxItems
+    if ((type.minItems > 0 && value.size() < type.minItems) || (type.maxItems > 0 && value.size() > type.maxItems))
+    {
+      CLog::Log(LOGWARNING, "JSONRPC: Number of array elements does not match minItems and/or maxItems in type %s", type.name);
+      if (type.minItems > 0 && type.maxItems > 0)
+        errorMessage.Format("Between %d and %d array items expected but %d received", type.minItems, type.maxItems, value.size());
+      else if (type.minItems > 0)
+        errorMessage.Format("At least %d array items expected but only %d received", type.minItems, value.size());
+      else
+        errorMessage.Format("Only %d array items expected but %d received", type.maxItems, value.size());
+      errorData["message"] = errorMessage.c_str();
+      return InvalidParams;
+    }
+
+    if (type.items.size() == 0)
+    {
+      outputValue = value;
+    }
+    else if (type.items.size() == 1)
+    {
+      JSONSchemaTypeDefinition itemType = type.items.at(0);
+
+      // Loop through all array elements
+      for (unsigned int arrayIndex = 0; arrayIndex < value.size(); arrayIndex++)
+      {
+        JSON_STATUS status = checkType(value[arrayIndex], itemType, outputValue[arrayIndex], errorData["property"]);
+        if (status != OK)
+        {
+          CLog::Log(LOGWARNING, "JSONRPC: Array element at index %u does not match in type %s", arrayIndex, type.name);
+          errorMessage.Format("%s expected for array element at index %u but %s received", SchemaValueTypeToString(type.type), arrayIndex, ValueTypeToString(value.type()));
+          errorData["message"] = errorMessage.c_str();
+          return status;
+        }
+      }
+    }
+    // We have more than one element in "items"
+    // so we have tuple typing, which means that
+    // every element in the value array must match
+    // with the type at the same position in the
+    // "items" array
+    else
+    {
+      // If the number of elements in the value array
+      // does not match the number of elements in the
+      // "items" array and additional items are not
+      // allowed there is no need to check every element
+      if (value.size() < type.items.size() || (value.size() != type.items.size() && type.additionalItems.size() == 0))
+      {
+        CLog::Log(LOGWARNING, "JSONRPC: One of the array elements does not match in type %s", type.name);
+        errorMessage.Format("%d array elements expected but %d received", type.items.size(), value.size());
+        errorData["message"] = errorMessage.c_str();
+        return InvalidParams;
+      }
+
+      // Loop through all array elements until there
+      // are either no more schemas in the "items"
+      // array or no more elements in the value's array
+      unsigned int arrayIndex;
+      for (arrayIndex = 0; arrayIndex < min(type.items.size(), value.size()); arrayIndex++)
+      {
+        JSON_STATUS status = checkType(value[arrayIndex], type.items.at(arrayIndex), outputValue[arrayIndex], errorData["property"]);
+        if (status != OK)
+        {
+          CLog::Log(LOGWARNING, "JSONRPC: Array element at index %u does not match with items schema in type %s", arrayIndex, type.name);
+          return status;
+        }
+      }
+
+      if (type.additionalItems.size() > 0)
+      {
+        // Loop through the rest of the elements
+        // in the array and check them against the
+        // "additionalItems"
+        for (; arrayIndex < value.size(); arrayIndex++)
+        {
+          bool ok = false;
+          for (unsigned int additionalIndex = 0; additionalIndex < type.additionalItems.size(); additionalIndex++)
+          {
+            Json::Value dummyError;
+            if (checkType(value[arrayIndex], type.additionalItems.at(additionalIndex), outputValue[arrayIndex], dummyError) == OK)
+            {
+              ok = true;
+              break;
+            }
+          }
+
+          if (!ok)
+          {
+            CLog::Log(LOGWARNING, "JSONRPC: Array contains non-conforming additional items in type %s", type.name);
+            errorMessage.Format("Array element at index %u does not match the \"additionalItems\" schema", arrayIndex);
+            errorData["message"] = errorMessage.c_str();
+            return InvalidParams;
+          }
+        }
+      }
+    }
+
+    // If every array element is unique we need to check each one
+    if (type.uniqueItems)
+    {
+      for (unsigned int checkingIndex = 0; checkingIndex < outputValue.size(); checkingIndex++)
+      {
+        for (unsigned int checkedIndex = checkingIndex + 1; checkedIndex < outputValue.size(); checkedIndex++)
+        {
+          // If two elements are the same they are not unique
+          if (Compare(outputValue[checkingIndex], outputValue[checkedIndex]) == 0)
+          {
+            CLog::Log(LOGWARNING, "JSONRPC: Not unique array element at index %u and %u in type %s", checkingIndex, checkedIndex, type.name);
+            errorMessage.Format("Array element at index %u is not unique (same as array element at index %u)", checkingIndex, checkedIndex);
+            errorData["message"] = errorMessage.c_str();
+            return InvalidParams;
+          }
+        }
+      }
+    }
+
+    return OK;
+  }
+
+  // If it is an object we need to check every element
+  // against the defined "properties"
+  if (HasType(type.type, ObjectValue) && value.isObject())
+  {
+    unsigned int handled = 0;
+    JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesEnd = type.properties.end();
+    JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesIterator;
+    for (propertiesIterator = type.properties.begin(); propertiesIterator != propertiesEnd; propertiesIterator++)
+    {
+      if (value.isMember(propertiesIterator->first))
+      {
+        JSON_STATUS status = checkType(value[propertiesIterator->first], propertiesIterator->second, outputValue[propertiesIterator->first], errorData["property"]);
+        if (status != OK)
+        {
+          CLog::Log(LOGWARNING, "JSONRPC: Invalid property \"%s\" in type %s", propertiesIterator->first.c_str(), type.name);
+          return status;
+        }
+        handled++;
+      }
+      else if (propertiesIterator->second.optional)
+        outputValue[propertiesIterator->first] = propertiesIterator->second.defaultValue;
+      else
+      {
+        CLog::Log(LOGWARNING, "JSONRPC: Missing property \"%s\" in type %s", propertiesIterator->first.c_str(), type.name);
+        errorData["property"]["name"] = propertiesIterator->first.c_str();
+        errorData["property"]["type"] = SchemaValueTypeToString(propertiesIterator->second.type);
+        errorData["message"] = "Missing property";
+        return InvalidParams;
+      }
+    }
+
+    // Additional properties are not allowed
+    if (handled < value.getMemberNames().size())
+    {
+      errorData["message"] = "Unexpected additional properties received";
+      return InvalidParams;
+    }
+
+    return OK;
+  }
+
+  // It's neither an array nor an object
+
+  // If it can only take certain values ("enum")
+  // we need to check against those
+  if (type.enums.size() > 0)
+  {
+    bool valid = false;
+    for (unsigned int enumIndex = 0; enumIndex < type.enums.size(); enumIndex++)
+    {
+      if (Compare(type.enums.at(enumIndex), value) == 0)
+      {
+        valid = true;
+        break;
+      }
+    }
+
+    if (!valid)
+    {
+      CLog::Log(LOGWARNING, "JSONRPC: Value does not match any of the enum values in type %s", type.name);
+      errorData["message"] = "Received value does not match any of the defined enum values";
+      return InvalidParams;
+    }
+  }
+
+  // If we have a number or an integer type, we need
+  // to check the minimum and maximum values
+  if (HasType(type.type, NumberValue) || HasType(type.type, IntegerValue) && value.isNumeric())
+  {
+    double numberValue = value.asDouble();
+    // Check minimum
+    if ((type.exclusiveMinimum && numberValue <= type.minimum) || (!type.exclusiveMinimum && numberValue < type.minimum) ||
+    // Check maximum
+        (type.exclusiveMaximum && numberValue >= type.maximum) || (!type.exclusiveMaximum && numberValue > type.maximum))        
+    {
+      CLog::Log(LOGWARNING, "JSONRPC: Value does not lay between minimum and maximum in type %s", type.name);
+      errorMessage.Format("Value between %f (%s) and %f (%s) expected but %f received", 
+        type.minimum, type.exclusiveMinimum ? "exclusive" : "inclusive", type.maximum, type.exclusiveMaximum ? "exclusive" : "inclusive", numberValue);
+      errorData["message"] = errorMessage.c_str();
+      return InvalidParams;
+    }
+    // Check divisibleBy
+    else if ((HasType(type.type, IntegerValue) && type.divisibleBy > 0 && ((int)numberValue % type.divisibleBy) != 0))
+    {
+      CLog::Log(LOGWARNING, "JSONRPC: Value does not meet divisibleBy requirements in type %s", type.name);
+      errorMessage.Format("Value should be divisible by %d but %d received", type.divisibleBy, (int)numberValue);
+      errorData["message"] = errorMessage.c_str();
+      return InvalidParams;
+    }
+  }
+
+  // Otherwise it can have any value
+  outputValue = value;
+  return OK;
+}
+
+void CJSONServiceDescription::parseHeader(const Json::Value &descriptionObject)
+{
+  m_header.id = GetString(descriptionObject["id"], "");
+  m_header.version = descriptionObject.get("version", 0).asInt();
+  m_header.description = GetString(descriptionObject["description"], "");
+}
+
+bool CJSONServiceDescription::parseMethod(const Json::Value &value, JsonRpcMethod &method)
+{
+  // Parse XBMC specific information about the method
+  method.transportneed = StringToTransportLayer(value.isMember("transport") ? value["transport"].asString().c_str() : "");
+  method.permission = StringToPermission(value.isMember("permission") ? value["permission"].asString().c_str() : "");
+  method.description = GetString(value["description"], "");
+  method.stateChanging = value.isMember("statechanging") ? value["statechanging"].asBool() : false;
+
+  // Check whether there are parameters defined
+  if (value.isMember("params") && value["params"].isArray())
+  {
+    // Loop through all defined parameters
+    for (unsigned int paramIndex = 0; paramIndex < value["params"].size(); paramIndex++)
+    {
+      Json::Value parameter = value["params"][paramIndex];
+      // If the parameter definition does not contain a valid "name" or
+      // "type" element we will ignore it
+      if (!parameter.isMember("name") || !parameter["name"].isString() ||
+          (!parameter.isMember("type") && !parameter.isMember("$ref")) ||
+          (parameter.isMember("type") && !parameter["type"].isString() &&
+          !parameter["type"].isArray()) || (parameter.isMember("$ref") &&
+          !parameter["$ref"].isString()))
+      {
+        CLog::Log(LOGWARNING, "JSONRPC: Method %s has a badly defined parameter", method.name);
+        return false;
+      }
+
+      // Parse the parameter and add it to the list
+      // of defined parameters
+      JSONSchemaTypeDefinition param;
+      if (!parseParameter(parameter, param))
+        return false;
+      method.parameters.push_back(param);
+    }
+  }
+    
+  // Parse the return value of the method
+  parseReturn(value, method.returns);
+
+  return true;
+}
+
+bool CJSONServiceDescription::parseParameter(Json::Value &value, JSONSchemaTypeDefinition &parameter)
+{
+  parameter.name = GetString(value["name"], "");
+
+  // Parse the type and default value of the parameter
+  return parseTypeDefinition(value, parameter, true);
+}
+
+bool CJSONServiceDescription::parseTypeDefinition(const Json::Value &value, JSONSchemaTypeDefinition &type, bool isParameter)
+{
+  bool isReferenceType = false;
+  bool hasReference = false;
+
+  type.id = NULL;
+  type.description = NULL;
+
+  // Check if the type of the parameter defines a json reference
+  // to a type defined somewhere else
+  if (value.isMember("$ref") && value["$ref"].isString())
+  {
+    // Get the name of the referenced type
+    CStdString refType = value["$ref"].asString();
+    // Check if the referenced type exists
+    std::map<CStdString, JSONSchemaTypeDefinition>::const_iterator iter = m_types.find(refType);
+    if (refType.length() <= 0 || iter == m_types.end())
+    {
+      CLog::Log(LOGDEBUG, "JSONRPC: JSON schema type %s references an unknown type %s", type.name, refType.c_str());
+      return false;
+    }
+    
+    const char *typeName = type.name;
+    type = iter->second;
+    if (strlen(typeName) > 0)
+      type.name = typeName;
+    hasReference = true;
+  }
+  else if (value.isMember("id") && value["id"].isString())
+  {
+    type.id = GetString(value["id"], "");
+    isReferenceType = true;
+  }
+
+  // Check if the "required" field has been defined
+  type.optional = value.isMember("required") && value["required"].isBool() ? !value["required"].asBool() : true;
+
+  // Get the "description"
+  if (!hasReference || (value.isMember("description") && value["description"].isString()))
+    type.description = GetString(value["description"], "");
+
+  if (hasReference)
+  {
+    // If there is a specific default value, read it
+    if (value.isMember("default") && IsType(value["default"], type.type))
+    {
+      bool ok = false;
+      if (type.enums.size() >= 0)
+        ok = true;
+      // If the type has an enum definition we must make
+      // sure that the default value is a valid enum value
+      else
+      {
+        for (unsigned int defIndex = 0; defIndex < type.enums.size(); defIndex++)
+        {
+          if (Compare(value["default"], type.enums.at(defIndex)) == 0)
+          {
+            ok = true;
+            break;
+          }
+        }
+      }
+
+      if (ok)
+        type.defaultValue = value["default"];
+    }
+
+    return true;
+  }
+
+  // Get the defined type of the parameter
+  if (value["type"].isArray())
+  {
+    int parsedType = 0;
+    // If the defined type is an array, we have
+    // to handle a union type
+    for (unsigned int typeIndex = 0; typeIndex < value["type"].size(); typeIndex++)
+    {
+      // If the type is a string try to parse it
+      if (value["type"][typeIndex].isString())
+        parsedType |= StringToSchemaValueType(value["type"][typeIndex].asString().c_str());
+      else
+        CLog::Log(LOGWARNING, "JSONRPC: Invalid type in union type definition of type %s", type.name);
+    }
+
+    type.type = (JSONSchemaType)parsedType;
+
+    // If the type has not been set yet
+    // set it to "any"
+    if (type.type == 0)
+      type.type = AnyValue;
+  }
+  else
+    type.type = value["type"].isString() ? StringToSchemaValueType(value["type"].asString().c_str()) : AnyValue;
+
+  if (type.type == ObjectValue)
+  {
+    // If the type definition is of type "object"
+    // and has a "properties" definition we need
+    // to handle these as well
+    if (value.isMember("properties") && value["properties"].isObject())
+    {
+      // Get all child elements of the "properties"
+      // object and loop through them
+      Json::Value::Members properties = value["properties"].getMemberNames();
+      for (unsigned int propertyIndex = 0; propertyIndex < properties.size(); propertyIndex++)
+      {
+        CStdString propertyName = properties.at(propertyIndex);
+        if (!value["properties"].isMember(propertyName))
+          continue;
+
+        // Create a new type definition, store the name
+        // of the current property into it, parse it
+        // recursively and add its default value
+        // to the current type's default value
+        JSONSchemaTypeDefinition propertyType;
+        propertyType.name = propertyName.c_str();
+        if (!parseTypeDefinition(value["properties"][propertyName], propertyType, false))
+          return false;
+        type.defaultValue[propertyName] = propertyType.defaultValue;
+        type.properties.add(propertyType);
+      }
+    }
+  }
+  else 
+  {
+    // If the defined parameter is an array
+    // we need to check for detailed definitions
+    // of the array items
+    if (type.type == ArrayValue)
+    {
+      // Check for "uniqueItems" field
+      if (value.isMember("uniqueItems") && value["uniqueItems"].isBool())
+        type.uniqueItems = value["uniqueItems"].asBool();
+      else
+        type.uniqueItems = false;
+
+      // Check for "additionalItems" field
+      if (value.isMember("additionalItems"))
+      {
+        // If it is an object, there is only one schema for it
+        if (value["additionalItems"].isObject())
+        {
+          JSONSchemaTypeDefinition additionalItem;
+          additionalItem.name = NULL;
+
+          if (parseTypeDefinition(value["additionalItems"], additionalItem, false))
+            type.additionalItems.push_back(additionalItem);
+        }
+        // If it is an array there may be multiple schema definitions
+        else if (value["additionalItems"].isArray())
+        {
+          for (unsigned int itemIndex = 0; itemIndex < value["additionalItems"].size(); itemIndex++)
+          {
+            JSONSchemaTypeDefinition additionalItem;
+            additionalItem.name = NULL;
+
+            if (parseTypeDefinition(value["additionalItems"][itemIndex], additionalItem, false))
+              type.additionalItems.push_back(additionalItem);
+          }
+        }
+        // If it is not a (array of) schema and not a bool (default value is false)
+        // it has an invalid value
+        else if (!value["additionalItems"].isBool())
+          CLog::Log(LOGWARNING, "Invalid \"additionalItems\" value for type %s", type.name);
+      }
+
+      // If the "items" field is a single object
+      // we can parse that directly
+      if (value.isMember("items"))
+      {
+        if (value["items"].isObject())
+        {
+          JSONSchemaTypeDefinition item;
+          item.name = NULL;
+
+          if (!parseTypeDefinition(value["items"], item, false))
+            return false;
+          type.items.push_back(item);
+        }
+        // Otherwise if it is an array we need to
+        // parse all elements and store them
+        else if (value["items"].isArray())
+        {
+          for (unsigned int itemIndex = 0; itemIndex < value["items"].size(); itemIndex++)
+          {
+            JSONSchemaTypeDefinition item;
+            item.name = NULL;
+
+            if (!parseTypeDefinition(value["items"][itemIndex], item, false))
+              return false;
+            type.items.push_back(item);
+          }
+        }
+      }
+
+      type.minItems = value.get("minItems", 0).asInt();
+      type.maxItems = value.get("maxItems", 0).asInt();
+    }
+    // The type is whether an object nor an array
+    else 
+    {
+      if ((type.type & NumberValue) == NumberValue || (type.type  & IntegerValue) == IntegerValue)
+      {
+        if ((type.type & NumberValue) == NumberValue)
+        {
+          type.minimum = value.get("minimum", -numeric_limits<double>::max()).asDouble();
+          type.maximum = value.get("maximum", numeric_limits<double>::max()).asDouble();
+        }
+        else if ((type.type  & IntegerValue) == IntegerValue)
+        {
+          type.minimum = value.get("minimum", numeric_limits<int>::min()).asInt();
+          type.maximum = value.get("maximum", numeric_limits<int>::max()).asInt();
+        }
+
+        type.exclusiveMinimum = value.get("exclusiveMinimum", false).asBool();
+        type.exclusiveMaximum = value.get("exclusiveMaximum", false).asBool();
+        type.divisibleBy = value.get("divisibleBy", 0).asUInt();
+      }
+
+      // If the type definition is neither an
+      // "object" nor an "array" we can check
+      // for an "enum" definition
+      if (value.isMember("enum") && value["enum"].isArray())
+      {
+        // Loop through all elements in the "enum" array
+        for (unsigned int enumIndex = 0; enumIndex < value["enum"].size(); enumIndex++)
+        {
+          // Check for duplicates and eliminate them
+          bool approved = true;
+          for (unsigned int approvedIndex = 0; approvedIndex < type.enums.size(); approvedIndex++)
+          {
+            if (Compare(value["enum"][enumIndex], type.enums.at(approvedIndex)) == 0)
+            {
+              approved = false;
+              break;
+            }
+          }
+
+          // Only add the current item to the enum value 
+          // list if it is not duplicate
+          if (approved)
+            type.enums.push_back(value["enum"][enumIndex]);
+        }
+      }
+    }
+
+    // If there is a definition for a default value and its type
+    // matches the type of the parameter we can parse it
+    bool ok = false;
+    if (value.isMember("default") && IsType(value["default"], type.type))
+    {
+      if (type.enums.size() >= 0)
+        ok = true;
+      // If the type has an enum definition we must make
+      // sure that the default value is a valid enum value
+      else
+      {
+        for (unsigned int defIndex = 0; defIndex < type.enums.size(); defIndex++)
+        {
+          if (Compare(value["default"], type.enums.at(defIndex)) == 0)
+          {
+            ok = true;
+            break;
+          }
+        }
+      }
+    }
+
+    if (ok)
+      type.defaultValue = value["default"];
+    else
+    {
+      // If the type of the default value definition does not
+      // match the type of the parameter we have to log this
+      if (value.isMember("default") && !IsType(value["default"], type.type))
+        CLog::Log(LOGWARNING, "JSONRPC: Parameter %s has an invalid default value", type.name);
+      
+      // If the type contains an "enum" we need to get the
+      // default value from the first enum value
+      if (type.enums.size() > 0)
+        type.defaultValue = type.enums.at(0);
+      // otherwise set a default value instead
+      else
+        SetDefaultValue(type.defaultValue, type.type);
+    }
+  }
+
+  if (isReferenceType)
+    addReferenceTypeDefinition(type);
+
+  return true;
+}
+
+void CJSONServiceDescription::parseReturn(const Json::Value &value, Json::Value &returns)
+{
+  // Only parse the "returns" definition if there is one
+  if (value.isMember("returns"))
+    returns = value["returns"];
+}
+
+void CJSONServiceDescription::addReferenceTypeDefinition(JSONSchemaTypeDefinition &typeDefinition)
+{
+  // If the given json value is no object or does not contain an "id" field
+  // of type string it is no valid type definition
+  if (typeDefinition.id == NULL || strlen(typeDefinition.id) <= 0)
+    return;
+
+  // If the id has already been defined we ignore the type definition
+  if (m_types.find(typeDefinition.id) != m_types.end())
+    return;
+
+  // Add the type to the list of type definitions
+  m_types[typeDefinition.id] = typeDefinition;
+  if (m_badMethods.size() > 0)
+    m_newReferenceType = true;
+}
+
+CJSONServiceDescription::CJsonRpcMethodMap::CJsonRpcMethodMap()
+{
+  m_actionmap = std::map<CStdString, JsonRpcMethod>();
+}
+
+void CJSONServiceDescription::CJsonRpcMethodMap::add(JsonRpcMethod &method)
+{
+  CStdString name = method.name;
+  name = name.ToLower();
+  m_actionmap[name] = method;
+}
+
+CJSONServiceDescription::CJsonRpcMethodMap::JsonRpcMethodIterator CJSONServiceDescription::CJsonRpcMethodMap::begin() const
+{
+  return m_actionmap.begin();
+}
+
+CJSONServiceDescription::CJsonRpcMethodMap::JsonRpcMethodIterator CJSONServiceDescription::CJsonRpcMethodMap::find(const CStdString& key) const
+{
+  return m_actionmap.find(key);
+}
+
+CJSONServiceDescription::CJsonRpcMethodMap::JsonRpcMethodIterator CJSONServiceDescription::CJsonRpcMethodMap::end() const
+{
+  return m_actionmap.end();
+}
diff --git a/xbmc/interfaces/json-rpc/JSONServiceDescription.h b/xbmc/interfaces/json-rpc/JSONServiceDescription.h
new file mode 100644 (file)
index 0000000..ce08efc
--- /dev/null
@@ -0,0 +1,360 @@
+#pragma once
+/*
+ *      Copyright (C) 2005-2010 Team XBMC
+ *      http://www.xbmc.org
+ *
+ *  This Program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This Program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with XBMC; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *  http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+#include "utils/StdString.h"
+#include <string>
+#include <vector>
+#include "jsoncpp/include/json/json.h"
+#include "JSONUtils.h"
+
+namespace JSONRPC
+{
+  /*! 
+   \ingroup jsonrpc
+   \brief Structure for the header of 
+   of a service mapping description.
+   
+   Represents the header of a service mapping
+   description containing its version, id,
+   description, transport, envelope, contentType,
+   target and additionalParameters fields.
+   */
+  typedef struct
+  {
+    /*!
+     \brief Identification of the published
+     service containing json rpc methods.
+     */
+    const char* id;
+    /*!
+     \brief Description of the published
+     service containing json rpc methods.
+     */
+    const char* description;
+
+    /*!
+     \brief Version of the published
+     service containing json rpc methods.
+     */
+    int version;
+  } JsonRpcDescriptionHeader;
+
+  /*! 
+   \ingroup jsonrpc
+   \brief Structure for a parameter of a
+   json rpc method.
+   
+   Represents a parameter of a defined
+   json rpc method and is used to verify
+   and extract the value of the parameter
+   in a method call.
+   */
+  typedef struct JSONSchemaTypeDefinition
+  {
+    /*!
+     \brief Name of the parameter (for 
+     by-name calls)
+     */
+    const char* name;
+
+    /*!
+     \brief Id of the type (for
+     referenced types)
+     */
+    const char* id;
+
+    /*!
+     \brief Description of the parameter
+     */
+    const char* description;
+
+    /*!
+     \brief JSON schema type of the parameter's value
+     */
+    JSONSchemaType type;
+
+    /*!
+     \brief Whether or not the parameter is
+     optional
+     */
+    bool optional;
+
+    /*!
+     \brief Default value of the parameter
+     (only needed when it is optional)
+     */
+    Json::Value defaultValue;
+
+    /*!
+     \brief Minimum value for Integer
+     or Number types
+     */
+    double minimum;
+
+    /*!
+     \brief Maximum value for Integer or Number types
+     */
+    double maximum;
+
+    /*!
+     \brief Whether to exclude the defined Minimum
+     value from the valid range or not
+     */
+    bool exclusiveMinimum;
+
+    /*!
+     \brief  Whether to exclude the defined Maximum
+     value from the valid range or not
+     */
+    bool exclusiveMaximum;
+
+    /*!
+     \brief Integer by which the value (of type
+     Integer) must be divisible without rest
+     */
+    unsigned int divisibleBy;
+
+    /*!
+     \brief (Optional) List of allowed values
+     for the type
+     */
+    std::vector<Json::Value> enums;
+
+    /*!
+     \brief List of possible values in an array
+     */
+    std::vector<JSONSchemaTypeDefinition> items;
+
+    /*!
+     \brief Minimum amount of items in the array
+     */
+    unsigned minItems;
+
+    /*!
+     \brief Maximum amount of items in the array
+     */
+    unsigned maxItems;
+
+    /*!
+     \brief Whether every value in the array
+     must be unique or not
+     */
+    bool uniqueItems;
+
+    /*!
+     \brief List of json schema definitions for
+     additional items in an array with tuple
+     typing (defined schemas in "items")
+     */
+    std::vector<JSONSchemaTypeDefinition> additionalItems;
+
+    /*!
+     \brief Maps a properties name to its
+     json schema type definition
+     */
+    class CJsonSchemaPropertiesMap
+    {
+    public:
+      CJsonSchemaPropertiesMap();
+
+      void add(JSONSchemaTypeDefinition &property);
+
+      typedef std::map<CStdString, JSONSchemaTypeDefinition>::const_iterator JSONSchemaPropertiesIterator;
+      JSONSchemaPropertiesIterator begin() const;
+      JSONSchemaPropertiesIterator find(const CStdString& key) const;
+      JSONSchemaPropertiesIterator end() const;
+      unsigned int size() const;
+    private:
+      std::map<CStdString, JSONSchemaTypeDefinition> m_propertiesmap;
+    };
+
+    /*!
+     \brief List of properties of the parameter (only needed when the
+     parameter is an object)
+     */
+    CJsonSchemaPropertiesMap properties;
+  } JSONSchemaTypeDefinition;
+
+  /*! 
+   \ingroup jsonrpc
+   \brief Structure for a published json
+   rpc method.
+   
+   Represents a published json rpc method
+   and is used to verify an incoming json
+   rpc request against a defined method.
+   */
+  typedef struct
+  {
+    /*!
+     \brief Name of the represented method
+     */
+    const char* name;
+    /*!
+     \brief Pointer tot he implementation
+     of the represented method
+     */
+    MethodCall method;
+    /*!
+     \brief Definition of the type of
+     request/response
+     */
+    TransportLayerCapability transportneed;
+    /*!
+     \brief Definition of the permissions needed
+     to execute the method
+     */
+    OperationPermission permission;
+         /*!
+          \brief Whether this method changes the internal
+     state of XBMC or not
+          */
+         bool stateChanging;
+    /*!
+     \brief Description of the method
+     */
+    const char* description;
+    /*!
+     \brief List of accepted parameters
+     */
+    std::vector<JSONSchemaTypeDefinition> parameters;
+    /*!
+     \brief Definition of the return value
+     */
+    Json::Value returns;
+  } JsonRpcMethod;
+
+  /*! 
+   \ingroup jsonrpc
+   \brief Structure mapping a json rpc method
+   definition to an actual method implementation.
+   */
+  typedef struct
+  {
+    /*!
+     \brief Name of the json rpc method.
+     */
+    const char* name;
+    /*!
+     \brief Pointer to the actual
+     implementation of the json rpc
+     method.
+     */
+    MethodCall method;
+  } JsonRpcMethodMap;
+
+  /*!
+   \ingroup jsonrpc
+   \brief Helper class for json schema service descriptor based
+   service descriptions for the json rpc API
+
+   Provides static functions to parse a complete json schema
+   service descriptor of a published service containing json rpc 
+   methods, print the json schema service descriptor representation 
+   into a string (mainly for output purposes) and evaluate and verify 
+   parameters provided in a call to one of the publish json rpc methods 
+   against a parameter definition parsed from a json schema service
+   descriptor.
+   */
+  class CJSONServiceDescription : public CJSONUtils
+  {
+  public:
+    /*!
+     \brief Parses the given json schema description and maps
+     the found methods against the given method map
+     \param serviceDescription json schema description to parse
+     \param methodMap Map of json rpc method names to actual C/C++ implementations
+     \param size Size of the method map
+     \return True if the json schema description has been parsed sucessfully otherwise false
+     */
+    static bool Parse(const JsonRpcMethodMap methodMap[], unsigned int size);
+
+    /*!
+     \brief Gets the version of the json
+     schema description
+     \return Version of the json schema description
+     */
+    static int GetVersion();
+
+    /*!
+     \brief Prints the json schema description into the given result object
+     \param result Object into which the json schema description is printed
+     \param transport Transport layer capabilities
+     \param client Client requesting a print
+     \param printDescriptions Whether to print descriptions or not
+     \param printMetadata Whether to print XBMC specific data or not
+     \param filterByTransport Whether to filter by transport or not
+     */
+    static void Print(Json::Value &result, ITransportLayer *transport, IClient *client, bool printDescriptions, bool printMetadata, bool filterByTransport);
+
+    /*!
+     \brief Checks the given parameters from the request against the
+     json schema description for the given method
+     \param method Called method
+     \param requestParameters Parameters from the request
+     \param client Client who sent the request
+     \param announcement Whether the request was sent as an announcement or not
+     \param methodCall Object which will contain the actual C/C++ method to be called
+     \param outputParameters Cleaned up parameter list
+     \return OK if the validation of the request succeeded otherwise an appropriate error code
+
+     Checks if the given method is a valid json rpc method, if the client has the permission
+     to call this method, if the method can be called as an announcement or not, assigns the
+     actual C/C++ implementation of the method to the "methodCall" parameter and checks the
+     given parameters from the request against the json schema description for the given method.
+     */
+    static JSON_STATUS CheckCall(const char* const method, const Json::Value &requestParameters, IClient *client, bool announcement, MethodCall &methodCall, Json::Value &outputParameters);
+
+  private:
+    static void printType(const JSONSchemaTypeDefinition &type, bool isParameter, bool isGlobal, bool printDefault, bool printDescriptions, Json::Value &output);
+    static JSON_STATUS checkParameter(const Json::Value &requestParameters, const JSONSchemaTypeDefinition &type, unsigned int position, Json::Value &outputParameters, unsigned int &handled, Json::Value &errorData);
+    static JSON_STATUS checkType(const Json::Value &value, const JSONSchemaTypeDefinition &type, Json::Value &outputValue, Json::Value &errorData);
+    static void parseHeader(const Json::Value &descriptionObject);
+    static bool parseMethod(const Json::Value &value, JsonRpcMethod &method);
+    static bool parseParameter(Json::Value &value, JSONSchemaTypeDefinition &parameter);
+    static bool parseTypeDefinition(const Json::Value &value, JSONSchemaTypeDefinition &type, bool isParameter);
+    static void parseReturn(const Json::Value &value, Json::Value &returns);
+    static void addReferenceTypeDefinition(JSONSchemaTypeDefinition &typeDefinition);
+
+    class CJsonRpcMethodMap
+    {
+    public:
+      CJsonRpcMethodMap();
+
+      void add(JsonRpcMethod &method);
+
+      typedef std::map<CStdString, JsonRpcMethod>::const_iterator JsonRpcMethodIterator;
+      JsonRpcMethodIterator begin() const;
+      JsonRpcMethodIterator find(const CStdString& key) const;
+      JsonRpcMethodIterator end() const;
+    private:
+      std::map<CStdString, JsonRpcMethod> m_actionmap;
+    };
+
+    static Json::Value m_notifications;
+    static JsonRpcDescriptionHeader m_header;
+    static CJsonRpcMethodMap m_actionMap;
+    static std::map<CStdString, JSONSchemaTypeDefinition> m_types;
+    static std::vector<CStdString> m_badMethods;
+    static bool m_newReferenceType;
+  };
+}
index f4d27d7..7dc5235 100644 (file)
  *
  */
 
-#include "JSONRPC.h"
+#include <string.h>
+#include <stdlib.h>
+#include "utils/StdString.h"
+#include "interfaces/IAnnouncer.h"
+#include "ITransportLayer.h"
+
+using namespace ANNOUNCEMENT;
 
 namespace JSONRPC
 {
+  /*!
+   \ingroup jsonrpc
+   \brief Possible statuc codes of a response
+   to a JSON RPC request
+   */
+  enum JSON_STATUS
+  {
+    OK = 0,
+    ACK = -1,
+    InvalidRequest = -32600,
+    MethodNotFound = -32601,
+    InvalidParams = -32602,
+    InternalError = -32603,
+    ParseError = -32700,
+    //-32099..-32000 Reserved for implementation-defined server-errors.
+    BadPermission = -32099,
+    FailedToExecute = -32100
+  };
+
+  /*!
+   \brief Function pointer for json rpc methods
+   */
+  typedef JSON_STATUS (*MethodCall) (const CStdString &method, ITransportLayer *transport, IClient *client, const Json::Value& parameterObject, Json::Value &result);
+
+  /*!
+   \ingroup jsonrpc
+   \brief Permission categories for json rpc methods
+   
+   A json rpc method will only be called if the caller 
+   has the correct permissions to exectue the method.
+   The method call needs to be perfectly threadsafe.
+  */
+  enum OperationPermission
+  {
+    ReadData = 0x1,
+    ControlPlayback = 0x2,
+    ControlAnnounce = 0x4,
+    ControlPower = 0x8,
+    Logging = 0x10,
+    ScanLibrary = 0x20,
+  };
+
+  static const int OPERATION_PERMISSION_ALL = (ReadData | ControlPlayback | ControlAnnounce | ControlPower | Logging | ScanLibrary);
+
+  /*!
+   \brief Possible value types of a parameter or return type
+   */
+  enum JSONSchemaType
+  {
+    NullValue = 0x01,
+    StringValue = 0x02,
+    NumberValue = 0x04,
+    IntegerValue = 0x08,
+    BooleanValue = 0x10,
+    ArrayValue = 0x20,
+    ObjectValue = 0x40,
+    AnyValue = 0x80
+  };
+
+  /*!
+   \ingroup jsonrpc
+   \brief Helper class containing utility methods to handle
+   json rpc method calls.*/
   class CJSONUtils
   {
   protected:
-    static inline bool ParameterIntOrNull(const Json::Value &parameterObject, const char *key) { return parameterObject[key].isInt() || parameterObject[key].isNull(); }
-    static inline int ParameterAsInt(const Json::Value &parameterObject, int fallback, const char *key) { return parameterObject[key].isInt() ? parameterObject[key].asInt() : fallback; }
+    /*!
+     \brief Checks if the given object contains a parameter
+     \param parameterObject Object to check for a parameter
+     \param key Possible name of the parameter
+     \param position Possible position of the parameter
+     \return True if the parameter is available otherwise false
+
+     Checks the given object for a parameter with the given key (if
+     the given object is not an array) or for a parameter at the 
+     given position (if the given object is an array).
+     */
+    static inline bool ParameterExists(const Json::Value &parameterObject, const char *key, unsigned int position) { return IsValueMember(parameterObject, key) || (parameterObject.isArray() && parameterObject.size() > position); }
+
+    /*!
+     \brief Checks if the given object contains a value
+     with the given key
+     \param value Value to check for the member
+     \param key Key of the member to check for
+     \return True if the given object contains a member with 
+     the given key otherwise false
+     */
+    static inline bool IsValueMember(const Json::Value &value, const char *key) { return value.isObject() && value.isMember(key); }
+    
+    /*!
+     \brief Returns the json value of a parameter
+     \param parameterObject Object containing all provided parameters
+     \param key Possible name of the parameter
+     \param position Possible position of the parameter
+     \return Json value of the parameter with the given name or at the
+     given position
+
+     Returns the value of the parameter with the given key (if
+     the given object is not an array) or of the parameter at the 
+     given position (if the given object is an array).
+     */
+    static inline Json::Value GetParameter(const Json::Value &parameterObject, const char *key, unsigned int position) { return IsValueMember(parameterObject, key) ? parameterObject[key] : parameterObject[position]; }
+    
+    /*!
+     \brief Returns the json value of a parameter or the given
+     default value
+     \param parameterObject Object containing all provided parameters
+     \param key Possible name of the parameter
+     \param position Possible position of the parameter
+     \param fallback Default value of the parameter
+     \return Json value of the parameter with the given name or at the
+     given position or the default value if the parameter does not exist
+
+     Returns the value of the parameter with the given key (if
+     the given object is not an array) or of the parameter at the 
+     given position (if the given object is an array). If the
+     parameter does not exist the given default value is returned.
+     */
+    static inline Json::Value GetParameter(const Json::Value &parameterObject, const char *key, unsigned int position, Json::Value fallback) { return IsValueMember(parameterObject, key) ? parameterObject[key] : ((parameterObject.isArray() && parameterObject.size() > position) ? parameterObject[position] : fallback); }
+    
+    /*!
+     \brief Returns the given json value as a string
+     \param value Json value to convert to a string
+     \param defaultValue Default string value
+     \return String value of the given json value or the default value
+     if the given json value is no string
+     */
+    static inline const char* GetString(const Json::Value &value, const char* defaultValue)
+    {
+      CStdString str = defaultValue;
+      if (value.isString())
+      {
+        str = value.asString();
+      }
+
+      return strcpy((char *)malloc(sizeof(char) * str.length() + 1), str.c_str());
+    }
+
+    /*!
+     \brief Returns a string representation for the 
+     given OperationPermission
+     \param permission Specific OperationPermission
+     \return String representation of the given OperationPermission
+     */
+    static inline const char* PermissionToString(const OperationPermission &permission)
+    {
+      switch (permission)
+      {
+      case ReadData:
+        return "ReadData";
+      case ControlPlayback:
+        return "ControlPlayback";
+      case ControlAnnounce:
+        return "ControlAnnounce";
+      case ControlPower:
+        return "ControlPower";
+      case Logging:
+        return "Logging";
+      case ScanLibrary:
+        return "ScanLibrary";
+      default:
+        return "Unknown";
+      }
+    }
+
+    /*!
+     \brief Returns a OperationPermission value for the given
+     string representation
+     \param permission String representation of the OperationPermission
+     \return OperationPermission value of the given string representation
+     */
+    static inline OperationPermission StringToPermission(const char *permission)
+    {
+      if (strcmp(permission, "ControlPlayback") == 0)
+        return ControlPlayback;
+      if (strcmp(permission, "ControlAnnounce") == 0)
+        return ControlAnnounce;
+      if (strcmp(permission, "ControlPower") == 0)
+        return ControlPower;
+      if (strcmp(permission, "Logging") == 0)
+        return Logging;
+      if (strcmp(permission, "ScanLibrary") == 0)
+        return ScanLibrary;
+
+      return ReadData;
+    }
+
+    /*!
+     \brief Returns a string representation for the 
+     given EAnnouncementFlag
+     \param announcement Specific EAnnouncementFlag
+     \return String representation of the given EAnnouncementFlag
+     */
+    static inline const char* AnnouncementFlagToString(const EAnnouncementFlag &announcement)
+    {
+      switch (announcement)
+      {
+      case Playback:
+        return "Playback";
+      case GUI:
+        return "GUI";
+      case System:
+        return "System";
+      case Library:
+        return "Library";
+      case Other:
+        return "Other";
+      default:
+        return "Unknown";
+      }
+    }
+
+    /*!
+     \brief Returns a TransportLayerCapability value of the
+     given string representation
+     \param transport String representation of the TransportLayerCapability
+     \return TransportLayerCapability value of the given string representation
+     */
+    static inline TransportLayerCapability StringToTransportLayer(const char *transport)
+    {
+      if (strcmp(transport, "Announcing") == 0)
+        return Announcing;
+      if (strcmp(transport, "FileDownload") == 0)
+        return FileDownload;
+
+      return Response;
+    }
+
+    /*!
+     \brief Returns a JSONSchemaType value for the given
+     string representation
+     \param valueType String representation of the JSONSchemaType
+     \return JSONSchemaType value of the given string representation
+     */
+    static inline JSONSchemaType StringToSchemaValueType(const char *valueType)
+    {
+      if (strcmp(valueType, "null") == 0)
+        return NullValue;
+      if (strcmp(valueType, "string") == 0)
+        return StringValue;
+      if (strcmp(valueType, "number") == 0)
+        return NumberValue;
+      if (strcmp(valueType, "integer") == 0)
+        return IntegerValue;
+      if (strcmp(valueType, "boolean") == 0)
+        return BooleanValue;
+      if (strcmp(valueType, "array") == 0)
+        return ArrayValue;
+      if (strcmp(valueType, "object") == 0)
+        return ObjectValue;
+
+      return AnyValue;
+    }
+    
+    /*!
+     \brief Returns a string representation for the 
+     given JSONSchemaType
+     \param valueType Specific JSONSchemaType
+     \return String representation of the given JSONSchemaType
+     */
+    static inline const char* SchemaValueTypeToString(JSONSchemaType valueType)
+    {
+      std::vector<JSONSchemaType> types = std::vector<JSONSchemaType>();
+      for (unsigned int value = 0x01; value <= (unsigned int)AnyValue; value *= 2)
+      {
+        if (HasType(valueType, (JSONSchemaType)value))
+          types.push_back((JSONSchemaType)value);
+      }
+
+      CStdString strType;
+      if (types.size() > 1)
+        strType.append("[");
+
+      for (unsigned int index = 0; index < types.size(); index++)
+      {
+        if (index > 0)
+          strType.append(", ");
+
+        switch (types.at(index))
+        {
+        case StringValue:
+          strType.append("string");
+          break;
+        case NumberValue:
+          strType.append("number");
+          break;
+        case IntegerValue:
+          strType.append("integer");
+          break;
+        case BooleanValue:
+          strType.append("boolean");
+          break;
+        case ArrayValue:
+          strType.append("array");
+          break;
+        case ObjectValue:
+          strType.append("object");
+          break;
+        case AnyValue:
+          strType.append("any");
+          break;
+        case NullValue:
+          strType.append("null");
+          break;
+        default:
+          strType.append("unknown");
+        }
+      }
+
+      if (types.size() > 1)
+        strType.append("]");
+
+      return strcpy((char *)malloc(sizeof(char) * strType.length() + 1), strType.c_str());
+    }
+
+    /*!
+     \brief Converts the given json schema type into
+     a json object
+     \param valueTye json schema type(s)
+     \param jsonObject json object into which the json schema type(s) are stored
+     */
+    static inline void SchemaValueTypeToJson(JSONSchemaType valueType, Json::Value &jsonObject)
+    {
+      jsonObject = Json::Value(Json::arrayValue);
+      for (unsigned int value = 0x01; value <= (unsigned int)AnyValue; value *= 2)
+      {
+        if (HasType(valueType, (JSONSchemaType)value))
+          jsonObject.append(SchemaValueTypeToString((JSONSchemaType)value));
+      }
+
+      if (jsonObject.size() == 1)
+        jsonObject = jsonObject[0];
+    }
+
+    static inline const char* ValueTypeToString(Json::ValueType valueType)
+    {
+      switch (valueType)
+      {
+      case Json::stringValue:
+        return "string";
+      case Json::realValue:
+        return "number";
+      case Json::intValue:
+      case Json::uintValue:
+        return "integer";
+      case Json::booleanValue:
+        return "boolean";
+      case Json::arrayValue:
+        return "array";
+      case Json::objectValue:
+        return "object";
+      case Json::nullValue:
+        return "null";
+      default:
+        return "unknown";
+      }
+    }
+
+    /*!
+     \brief Checks if the parameter with the given name or at
+     the given position is of a certain type
+     \param parameterObject Object containing all provided parameters
+     \param key Possible name of the parameter
+     \param position Possible position of the parameter
+     \param valueType Expected type of the parameter
+     \return True if the specific parameter is of the given type otherwise false
+     */
+    static inline bool IsParameterType(const Json::Value &parameterObject, const char *key, unsigned int position, JSONSchemaType valueType)
+    {
+      if ((valueType & AnyValue) == AnyValue)
+        return true;
+
+      Json::Value parameter;
+      if (IsValueMember(parameterObject, key))
+        parameter = parameterObject[key];
+      else if(parameterObject.isArray() && parameterObject.size() > position)
+        parameter = parameterObject[position];
+
+      return IsType(parameter, valueType);
+    }
+
+    /*!
+     \brief Checks if the given json value is of the given type
+     \param value Json value to check
+     \param valueType Expected type of the json value
+     \return True if the given json value is of the given type otherwise false
+    */
+    static inline bool IsType(const Json::Value &value, JSONSchemaType valueType)
+    {
+      if (HasType(valueType, AnyValue))
+        return true;
+      if (HasType(valueType, StringValue) && value.isString())
+        return true;
+      if (HasType(valueType, NumberValue) && (value.isInt() || value.isUInt() || value.isDouble()))
+        return true;
+      if (HasType(valueType, IntegerValue) && (value.isInt() || value.isUInt()))
+        return true;
+      if (HasType(valueType, BooleanValue) && value.isBool())
+        return true;
+      if (HasType(valueType, ArrayValue) && value.isArray())
+        return true;
+      if (HasType(valueType, ObjectValue) && value.isObject())
+        return true;
+
+      return value.isNull();
+    }
+
+    /*!
+     \brief Sets the value of the given json value to the
+     default value of the given type
+     \param value Json value to be set
+     \param valueType Type of the default value
+     */
+    static inline void SetDefaultValue(Json::Value &value, JSONSchemaType valueType)
+    {
+      switch (valueType)
+      {
+        case StringValue:
+          value = Json::Value("");
+          break;
+        case NumberValue:
+          value = Json::Value(Json::realValue);
+          break;
+        case IntegerValue:
+          value = Json::Value(Json::intValue);
+          break;
+        case BooleanValue:
+          value = Json::Value(Json::booleanValue);
+          break;
+        case ArrayValue:
+          value = Json::Value(Json::arrayValue);
+          break;
+        case ObjectValue:
+          value = Json::Value(Json::objectValue);
+          break;
+        default:
+          value = Json::Value(Json::nullValue);
+      }
+    }
+
+    static inline bool HasType(JSONSchemaType typeObject, JSONSchemaType type) { return (typeObject & type) == type; }
+
+    static inline int Compare(const Json::Value &value, const Json::Value &other)
+    {
+      if (value.type() != other.type())
+        return other.type() - value.type();
+
+      int result = 0;
+      Json::Value::Members members, otherMembers;
+
+      switch (value.type())
+      {
+      case Json::nullValue:
+        return 0;
+      case Json::intValue:
+        return other.asInt() - value.asInt();
+      case Json::uintValue:
+        return other.asUInt() - value.asUInt();
+      case Json::realValue:
+        return (int)(other.asDouble() - value.asDouble());
+      case Json::booleanValue:
+        return other.asBool() - value.asBool();
+      case Json::stringValue:
+        return other.asString().compare(value.asString());
+      case Json::arrayValue:
+        if (other.size() != value.size())
+          return other.size() - value.size();
+
+        for (unsigned int i = 0; i < other.size(); i++)
+        {
+          if ((result = value.get(i, NULL).compare(other[i])) != 0)
+            return result;
+        }
+
+        return 0;
+      case Json::objectValue:
+        members = value.getMemberNames();
+        otherMembers = other.getMemberNames();
+
+        if (members.size() != otherMembers.size())
+          return otherMembers.size() - members.size();
+
+        for (unsigned int i = 0; i < members.size(); i++)
+        {
+          if (!other.isMember(members.at(i)))
+            return -1;
+
+          if ((result = value.get(members.at(i), NULL).compare(other[members.at(i)])) != 0)
+            return result;
+        }
+
+        return 0;
+      }
 
-    static inline const Json::Value ForceObject(const Json::Value &value) { return value.isObject() ? value : Json::Value(Json::objectValue); }
+      return -1;  // unreachable
+    }
   };
 }
index a7deedd..a96edce 100644 (file)
@@ -4,6 +4,7 @@ SRCS=AudioLibrary.cpp \
      FileItemHandler.cpp \
      FileOperations.cpp \
      JSONRPC.cpp \
+     JSONServiceDescription.cpp \
      PicturePlayerOperations.cpp \
      PlayerOperations.cpp \
      PlaylistOperations.cpp \
index 8d22d96..7d4fe02 100644 (file)
@@ -117,16 +117,14 @@ JSON_STATUS CPlaylistOperations::GetItems(const CStdString &method, ITransportLa
 
 JSON_STATUS CPlaylistOperations::Add(const CStdString &method, ITransportLayer *transport, IClient *client, const Value &parameterObject, Value &result)
 {
-  Value param = ForceObject(parameterObject);
-
   CSingleLock lock(VirtualCriticalSection);
-  CPlayListPtr playlist = GetPlaylist(param);
-  param.removeMember(PLAYLIST_MEMBER_VIRTUAL);
+  CPlayListPtr playlist = GetPlaylist(parameterObject);
+  //parameterObject.removeMember(PLAYLIST_MEMBER_VIRTUAL);
 
   if (playlist)
   {
     CFileItemList list;
-    if (CFileItemHandler::FillFileItemList(param, list) && list.Size() > 0)
+    if (CFileItemHandler::FillFileItemList(parameterObject, list) && list.Size() > 0)
       playlist->Add(list);
 
     return ACK;
@@ -137,19 +135,18 @@ JSON_STATUS CPlaylistOperations::Add(const CStdString &method, ITransportLayer *
 
 JSON_STATUS CPlaylistOperations::Remove(const CStdString &method, ITransportLayer *transport, IClient *client, const Value &parameterObject, Value &result)
 {
-  const Value param = ForceObject(parameterObject);
-  if (!(param["item"].isInt() || param["item"].isString()))
+  if (!(parameterObject["item"].isInt() || parameterObject["item"].isString()))
     return InvalidParams;
 
   CSingleLock lock(VirtualCriticalSection);
-  CPlayListPtr playlist = GetPlaylist(param);
+  CPlayListPtr playlist = GetPlaylist(parameterObject);
 
   if (playlist)
   {
-    if (param["item"].isInt())
-      playlist->Remove(param["item"].asInt());
-    else if (param["item"].isString())
-      playlist->Remove(param["item"].asString());
+    if (parameterObject["item"].isInt())
+      playlist->Remove(parameterObject["item"].asInt());
+    else if (parameterObject["item"].isString())
+      playlist->Remove(parameterObject["item"].asString());
     return ACK;
   }
 
@@ -158,14 +155,13 @@ JSON_STATUS CPlaylistOperations::Remove(const CStdString &method, ITransportLaye
 
 JSON_STATUS CPlaylistOperations::Swap(const CStdString &method, ITransportLayer *transport, IClient *client, const Value &parameterObject, Value &result)
 {
-  const Value param = ForceObject(parameterObject);
-  if (!param["item1"].isInt() && !param["item2"].isInt())
+  if (!parameterObject["item1"].isInt() && !parameterObject["item2"].isInt())
     return InvalidParams;
 
   CSingleLock lock(VirtualCriticalSection);
-  CPlayListPtr playlist = GetPlaylist(param);
+  CPlayListPtr playlist = GetPlaylist(parameterObject);
 
-  if (playlist && playlist->Swap(param["item1"].asInt(), param["item2"].asInt()))
+  if (playlist && playlist->Swap(parameterObject["item1"].asInt(), parameterObject["item2"].asInt()))
     return ACK;
 
   return InvalidParams;
@@ -249,15 +245,14 @@ bool CPlaylistOperations::FillFileItemList(const Value &parameterObject, CFileIt
 
 CPlayListPtr CPlaylistOperations::GetPlaylist(const Value &parameterObject)
 {
-  const Value param = ForceObject(parameterObject);
-  if (param[PLAYLIST_MEMBER_VIRTUAL].isString())
+  if (parameterObject[PLAYLIST_MEMBER_VIRTUAL].isString())
   {
-    CStdString id = param[PLAYLIST_MEMBER_VIRTUAL].asString();
+    CStdString id = parameterObject[PLAYLIST_MEMBER_VIRTUAL].asString();
     return VirtualPlaylists[id];
   }
-  else if (param[PLAYLIST_MEMBER_FILE].isString())
+  else if (parameterObject[PLAYLIST_MEMBER_FILE].isString())
   {
-    CStdString file = param[PLAYLIST_MEMBER_FILE].asString();
+    CStdString file = parameterObject[PLAYLIST_MEMBER_FILE].asString();
     CPlayListPtr playlist = CPlayListPtr(CPlayListFactory::Create(file));
     if (playlist && playlist->Load(file))
       return playlist;
diff --git a/xbmc/interfaces/json-rpc/ServiceDescription.h b/xbmc/interfaces/json-rpc/ServiceDescription.h
new file mode 100644 (file)
index 0000000..eac0b3e
--- /dev/null
@@ -0,0 +1,134 @@
+#pragma once
+/*
+ *      Copyright (C) 2005-2010 Team XBMC
+ *      http://www.xbmc.org
+ *
+ *  This Program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This Program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with XBMC; see the file COPYING.  If not, write to
+ *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+ *  http://www.gnu.org/copyleft/gpl.html
+ *
+ */
+
+namespace JSONRPC
+{
+  const char* const JSON_SERVICE_DESCRIPTION = 
+  "{"
+    "\"id\": \"http://www.xbmc.org/jsonrpc.jsd\","
+    "\"version\": 3,"
+    "\"description\": \"JSON RPC API of XBMC\","
+
+    "\"JSONRPC.Introspect\": {"
+        "\"type\": \"method\","
+        "\"description\": \"Enumerates all actions and descriptions\","
+        "\"transport\": \"Response\","
+        "\"permission\": \"ReadData\","
+        "\"statechanging\": false,"
+        "\"params\": ["
+            "{ \"name\": \"getdescriptions\", \"type\": \"boolean\", \"default\": true },"
+            "{ \"name\": \"getmetadata\", \"type\": \"boolean\", \"default\": false },"
+            "{ \"name\": \"filterbytransport\", \"type\": \"boolean\", \"default\": true }"
+        "],"
+        "\"returns\": \"object\""
+    "},"
+    "\"JSONRPC.Version\": {"
+        "\"type\": \"method\","
+        "\"description\": \"Retrieve the jsonrpc protocol version\","
+        "\"transport\": \"Response\","
+        "\"permission\": \"ReadData\","
+        "\"statechanging\": false,"
+        "\"params\": [],"
+        "\"returns\": \"string\""
+    "},"
+    "\"JSONRPC.Permission\": {"
+        "\"type\": \"method\","
+        "\"description\": \"Retrieve the clients permissions\","
+        "\"transport\": \"Response\","
+        "\"permission\": \"ReadData\","
+        "\"statechanging\": false,"
+        "\"params\": [],"
+        "\"returns\": {"
+            "\"type\": \"object\","
+            "\"properties\": {"
+                "\"ReadData\": { \"type\": \"boolean\", \"required\": true, \"description\": \"\" },"
+                "\"ControlPlayback\": { \"type\": \"boolean\", \"required\": true, \"description\": \"\" },"
+                "\"ControlAnnounce\": { \"type\": \"boolean\", \"required\": true, \"description\": \"\" },"
+                "\"ControlPower\": { \"type\": \"boolean\", \"required\": true, \"description\": \"\" },"
+                "\"Logging\": { \"type\": \"boolean\", \"required\": true, \"description\": \"\" },"
+                "\"ScanLibrary\": { \"type\": \"boolean\", \"required\": true, \"description\": \"\" }"
+            "}"
+        "}"
+    "},"
+    "\"JSONRPC.Ping\": {"
+        "\"type\": \"method\","
+        "\"description\": \"Ping responder\","
+        "\"transport\": \"Response\","
+        "\"permission\": \"ReadData\","
+        "\"statechanging\": false,"
+        "\"params\": [],"
+        "\"returns\": \"string\""
+    "},"
+    "\"JSONRPC.GetAnnouncementFlags\": {"
+        "\"type\": \"method\","
+        "\"description\": \"Get announcement flags\","
+        "\"transport\": \"Announcing\","
+        "\"permission\": \"ReadData\","
+        "\"statechanging\": false,"
+        "\"params\": [],"
+        "\"returns\": {"
+            "\"id\": \"Announcement.Flags\","
+            "\"type\": \"object\","
+            "\"properties\": {"
+                "\"Playback\": { \"type\": \"boolean\", \"required\": true, \"description\": \"\" },"
+                "\"GUI\": { \"type\": \"boolean\", \"required\": true, \"description\": \"\" },"
+                "\"System\": { \"type\": \"boolean\", \"required\": true, \"description\": \"\" },"
+                "\"Library\": { \"type\": \"boolean\", \"required\": true, \"description\": \"\" },"
+                "\"Other\": { \"type\": \"boolean\", \"required\": true, \"description\": \"\" }"
+            "}"
+        "}"
+    "},"
+    "\"JSONRPC.SetAnnouncementFlags\": {"
+        "\"type\": \"method\","
+        "\"description\": \"Change the announcement flags\","
+        "\"transport\": \"Announcing\","
+        "\"permission\": \"ControlAnnounce\","
+        "\"statechanging\": true,"
+        "\"params\": ["
+            "{ \"name\": \"Playback\", \"type\": \"boolean\", \"default\": false },"
+            "{ \"name\": \"GUI\", \"type\": \"boolean\", \"default\": false },"
+            "{ \"name\": \"System\", \"type\": \"boolean\", \"default\": false },"
+            "{ \"name\": \"Library\", \"type\": \"boolean\", \"default\": false },"
+            "{ \"name\": \"Other\", \"type\": \"boolean\", \"default\": false }"
+        "],"
+        "\"returns\": { \"$ref\": \"Announcement.Flags\" }"
+    "},"
+    "\"JSONRPC.Announce\": {"
+        "\"type\": \"method\","
+        "\"description\": \"Announce to other connected clients\","
+        "\"transport\": \"Response\","
+        "\"permission\": \"ReadData\","
+        "\"statechanging\": false,"
+        "\"params\": ["
+            "{ \"name\": \"sender\", \"type\": \"string\", \"required\": true },"
+            "{ \"name\": \"message\", \"type\": \"string\", \"required\": true },"
+            "{ \"name\": \"data\", \"type\": \"any\", \"default\": null }"
+        "],"
+        "\"returns\": \"any\""
+    "}"
+  "}";
+
+  const char* const JSON_NOTIFICATION_DESCRIPTION =
+  "{"
+
+  "}";
+}
index 57e8152..a23e17f 100644 (file)
@@ -129,11 +129,10 @@ JSON_STATUS CVideoLibrary::GetSeasons(const CStdString &method, ITransportLayer
   if (!(parameterObject.isObject() || parameterObject.isNull()))
     return InvalidParams;
 
-  const Value param = ForceObject(parameterObject);
-  if (!ParameterIntOrNull(param, "tvshowid"))
-    return InvalidParams;
+  //if (!ParameterIntOrNull(parameterObject, "tvshowid"))
+  //  return InvalidParams;
 
-  int tvshowID = ParameterAsInt(param, -1, "tvshowid");
+  int tvshowID = parameterObject.get("tvshowid", -1).asInt();
 
   CVideoDatabase videodatabase;
   if (!videodatabase.Open())
@@ -141,7 +140,7 @@ JSON_STATUS CVideoLibrary::GetSeasons(const CStdString &method, ITransportLayer
 
   CFileItemList items;
   if (videodatabase.GetSeasonsNav("videodb://", items, -1, -1, -1, -1, tvshowID))
-    HandleFileItemList(NULL, false, "seasons", items, param, result);
+    HandleFileItemList(NULL, false, "seasons", items, parameterObject, result);
 
   videodatabase.Close();
   return OK;
@@ -152,12 +151,11 @@ JSON_STATUS CVideoLibrary::GetEpisodes(const CStdString &method, ITransportLayer
   if (!(parameterObject.isObject() || parameterObject.isNull()))
     return InvalidParams;
 
-  const Value param = ForceObject(parameterObject);
-  if (!(ParameterIntOrNull(param, "tvshowid") || ParameterIntOrNull(param, "season")))
-    return InvalidParams;
+  //if (!(ParameterIntOrNull(parameterObject, "tvshowid") || ParameterIntOrNull(parameterObject, "season")))
+  //  return InvalidParams;
 
-  int tvshowID = ParameterAsInt(param, -1, "tvshowid");
-  int season   = ParameterAsInt(param, -1, "season");
+  int tvshowID = parameterObject.get("tvshowid", -1).asInt();
+  int season   = parameterObject.get("season", -1).asInt();
 
   CVideoDatabase videodatabase;
   if (!videodatabase.Open())
@@ -165,7 +163,7 @@ JSON_STATUS CVideoLibrary::GetEpisodes(const CStdString &method, ITransportLayer
 
   CFileItemList items;
   if (videodatabase.GetEpisodesNav("videodb://", items, -1, -1, -1, -1, tvshowID, season))
-    HandleFileItemList("episodeid", true, "episodes", items, param, result);
+    HandleFileItemList("episodeid", true, "episodes", items, parameterObject, result);
 
   videodatabase.Close();
   return OK;
@@ -205,12 +203,11 @@ JSON_STATUS CVideoLibrary::GetMusicVideos(const CStdString &method, ITransportLa
   if (!(parameterObject.isObject() || parameterObject.isNull()))
     return InvalidParams;
 
-  const Value param = ForceObject(parameterObject);
-  if (!(ParameterIntOrNull(param, "artistid") || ParameterIntOrNull(param, "albumid")))
-    return InvalidParams;
+  //if (!(ParameterIntOrNull(parameterObject, "artistid") || ParameterIntOrNull(parameterObject, "albumid")))
+  //  return InvalidParams;
 
-  int artistID = ParameterAsInt(param, -1, "artistid");
-  int albumID  = ParameterAsInt(param, -1, "albumid");
+  int artistID = parameterObject.get("artistid", -1).asInt();
+  int albumID  = parameterObject.get("albumid", -1).asInt();
 
   CVideoDatabase videodatabase;
   if (!videodatabase.Open())
@@ -218,7 +215,7 @@ JSON_STATUS CVideoLibrary::GetMusicVideos(const CStdString &method, ITransportLa
 
   CFileItemList items;
   if (videodatabase.GetMusicVideosNav("videodb://", items, -1, -1, artistID, -1, -1, albumID))
-    HandleFileItemList("musicvideoid", true, "musicvideos", items, param, result);
+    HandleFileItemList("musicvideoid", true, "musicvideos", items, parameterObject, result);
 
   videodatabase.Close();
   return OK;
@@ -306,9 +303,9 @@ bool CVideoLibrary::FillFileItemList(const Value &parameterObject, CFileItemList
   CVideoDatabase videodatabase;
   if ((parameterObject["movieid"].isInt() || parameterObject["episodeid"].isInt() || parameterObject["musicvideoid"].isInt()) && videodatabase.Open())
   {
-    int movieID       = ParameterAsInt(parameterObject, -1, "movieid");
-    int episodeID     = ParameterAsInt(parameterObject, -1, "episodeid");
-    int musicVideoID  = ParameterAsInt(parameterObject, -1, "musicvideoid");
+    int movieID       = parameterObject.get("movieid", -1).asInt();
+    int episodeID     = parameterObject.get("episodeid", -1).asInt();
+    int musicVideoID  = parameterObject.get("musicvideoid", -1).asInt();
 
     bool success = true;
     if (movieID > 0)