1 /**
2  * Orthanc - A Lightweight, RESTful DICOM Store
3  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
4  * Department, University Hospital of Liege, Belgium
5  * Copyright (C) 2017-2020 Osimis S.A., Belgium
6  *
7  * This program is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public License
9  * as published by the Free Software Foundation, either version 3 of
10  * the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this program. If not, see
19  * <http://www.gnu.org/licenses/>.
20  **/
21 
22 
23 #include "../PrecompiledHeaders.h"
24 #include "LuaFunctionCall.h"
25 
26 #include "../OrthancException.h"
27 #include "../Logging.h"
28 
29 #if ORTHANC_ENABLE_DCMTK == 1
30 #  include "../DicomParsing/FromDcmtkBridge.h"
31 #endif
32 
33 #include <cassert>
34 #include <stdio.h>
35 #include <boost/lexical_cast.hpp>
36 
37 namespace Orthanc
38 {
CheckAlreadyExecuted()39   void LuaFunctionCall::CheckAlreadyExecuted()
40   {
41     if (isExecuted_)
42     {
43       throw OrthancException(ErrorCode_LuaAlreadyExecuted);
44     }
45   }
46 
LuaFunctionCall(LuaContext & context,const char * functionName)47   LuaFunctionCall::LuaFunctionCall(LuaContext& context,
48                                    const char* functionName) :
49     context_(context),
50     isExecuted_(false)
51   {
52     // Clear the stack to fulfill the invariant
53     lua_settop(context_.lua_, 0);
54     lua_getglobal(context_.lua_, functionName);
55   }
56 
PushString(const std::string & value)57   void LuaFunctionCall::PushString(const std::string& value)
58   {
59     CheckAlreadyExecuted();
60     lua_pushlstring(context_.lua_, value.c_str(), value.size());
61   }
62 
PushBoolean(bool value)63   void LuaFunctionCall::PushBoolean(bool value)
64   {
65     CheckAlreadyExecuted();
66     lua_pushboolean(context_.lua_, value);
67   }
68 
PushInteger(int value)69   void LuaFunctionCall::PushInteger(int value)
70   {
71     CheckAlreadyExecuted();
72     lua_pushinteger(context_.lua_, value);
73   }
74 
PushDouble(double value)75   void LuaFunctionCall::PushDouble(double value)
76   {
77     CheckAlreadyExecuted();
78     lua_pushnumber(context_.lua_, value);
79   }
80 
PushJson(const Json::Value & value)81   void LuaFunctionCall::PushJson(const Json::Value& value)
82   {
83     CheckAlreadyExecuted();
84     context_.PushJson(value);
85   }
86 
ExecuteInternal(int numOutputs)87   void LuaFunctionCall::ExecuteInternal(int numOutputs)
88   {
89     CheckAlreadyExecuted();
90 
91     assert(lua_gettop(context_.lua_) >= 1);
92     int nargs = lua_gettop(context_.lua_) - 1;
93     int error = lua_pcall(context_.lua_, nargs, numOutputs, 0);
94 
95     if (error)
96     {
97       assert(lua_gettop(context_.lua_) >= 1);
98 
99       std::string description(lua_tostring(context_.lua_, -1));
100       lua_pop(context_.lua_, 1); /* pop error message from the stack */
101 
102       throw OrthancException(ErrorCode_CannotExecuteLua, description);
103     }
104 
105     if (lua_gettop(context_.lua_) < numOutputs)
106     {
107       throw OrthancException(ErrorCode_LuaBadOutput);
108     }
109 
110     isExecuted_ = true;
111   }
112 
ExecutePredicate()113   bool LuaFunctionCall::ExecutePredicate()
114   {
115     ExecuteInternal(1);
116 
117     if (!lua_isboolean(context_.lua_, 1))
118     {
119       throw OrthancException(ErrorCode_NotLuaPredicate);
120     }
121 
122     return lua_toboolean(context_.lua_, 1) != 0;
123   }
124 
125 
ExecuteToJson(Json::Value & result,bool keepStrings)126   void LuaFunctionCall::ExecuteToJson(Json::Value& result,
127                                       bool keepStrings)
128   {
129     ExecuteInternal(1);
130     context_.GetJson(result, context_.lua_, lua_gettop(context_.lua_), keepStrings);
131   }
132 
133 
ExecuteToString(std::string & result)134   void LuaFunctionCall::ExecuteToString(std::string& result)
135   {
136     ExecuteInternal(1);
137 
138     int top = lua_gettop(context_.lua_);
139     if (lua_isstring(context_.lua_, top))
140     {
141       result = lua_tostring(context_.lua_, top);
142     }
143     else
144     {
145       throw OrthancException(ErrorCode_LuaReturnsNoString);
146     }
147   }
148 
149 
PushStringMap(const std::map<std::string,std::string> & value)150   void LuaFunctionCall::PushStringMap(const std::map<std::string, std::string>& value)
151   {
152     Json::Value json = Json::objectValue;
153 
154     for (std::map<std::string, std::string>::const_iterator
155            it = value.begin(); it != value.end(); ++it)
156     {
157       json[it->first] = it->second;
158     }
159 
160     PushJson(json);
161   }
162 
163 
PushDicom(const DicomMap & dicom)164   void LuaFunctionCall::PushDicom(const DicomMap& dicom)
165   {
166     DicomArray a(dicom);
167     PushDicom(a);
168   }
169 
170 
PushDicom(const DicomArray & dicom)171   void LuaFunctionCall::PushDicom(const DicomArray& dicom)
172   {
173     Json::Value value = Json::objectValue;
174 
175     for (size_t i = 0; i < dicom.GetSize(); i++)
176     {
177       const DicomValue& v = dicom.GetElement(i).GetValue();
178       std::string s = (v.IsNull() || v.IsBinary()) ? "" : v.GetContent();
179       value[dicom.GetElement(i).GetTag().Format()] = s;
180     }
181 
182     PushJson(value);
183   }
184 
Execute()185   void LuaFunctionCall::Execute()
186   {
187     ExecuteInternal(0);
188   }
189 
190 
191 #if ORTHANC_ENABLE_DCMTK == 1
ExecuteToDicom(DicomMap & target)192   void LuaFunctionCall::ExecuteToDicom(DicomMap& target)
193   {
194     Json::Value output;
195     ExecuteToJson(output, true /* keep strings */);
196 
197     target.Clear();
198 
199     if (output.type() == Json::arrayValue &&
200         output.size() == 0)
201     {
202       // This case happens for empty tables
203       return;
204     }
205 
206     if (output.type() != Json::objectValue)
207     {
208       throw OrthancException(ErrorCode_LuaBadOutput,
209                              "Lua: The script must return a table");
210     }
211 
212     Json::Value::Members members = output.getMemberNames();
213 
214     for (size_t i = 0; i < members.size(); i++)
215     {
216       if (output[members[i]].type() != Json::stringValue)
217       {
218         throw OrthancException(ErrorCode_LuaBadOutput,
219                                "Lua: The script must return a table "
220                                "mapping names of DICOM tags to strings");
221       }
222 
223       DicomTag tag(FromDcmtkBridge::ParseTag(members[i]));
224       target.SetValue(tag, output[members[i]].asString(), false);
225     }
226   }
227 #endif
228 }
229