bes Updated for version 3.20.13
BESXMLInterface.cc
1// BESXMLInterface.cc
2
3// This file is part of bes, A C++ back-end server implementation framework
4// for the OPeNDAP Data Access Protocol.
5
6// Copyright (c) 2004-2009 University Corporation for Atmospheric Research
7// Author: Patrick West <pwest@ucar.edu> and Jose Garcia <jgarcia@ucar.edu>
8//
9// This library is free software; you can redistribute it and/or
10// modify it under the terms of the GNU Lesser General Public
11// License as published by the Free Software Foundation; either
12// version 2.1 of the License, or (at your option) any later version.
13//
14// This library is distributed in the hope that it will be useful,
15// but WITHOUT ANY WARRANTY; without even the implied warranty of
16// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17// Lesser General Public License for more details.
18//
19// You should have received a copy of the GNU Lesser General Public
20// License along with this library; if not, write to the Free Software
21// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22//
23// You can contact University Corporation for Atmospheric Research at
24// 3080 Center Green Drive, Boulder, CO 80301
25
26// (c) COPYRIGHT University Corporation for Atmospheric Research 2004-2005
27// Please read the full copyright statement in the file COPYRIGHT_UCAR.
28//
29// Authors:
30// pwest Patrick West <pwest@ucar.edu>
31// jgarcia Jose Garcia <jgarcia@ucar.edu>
32
33#include "config.h"
34
35#include <iostream>
36#include <sstream>
37
38using namespace std;
39
40#include "BESXMLInterface.h"
41#include "BESXMLCommand.h"
42#include "BESXMLUtils.h"
43#include "BESDataNames.h"
44#include "BESResponseNames.h"
45#include "BESContextManager.h"
46
47#include "BESResponseHandler.h"
48#include "BESReturnManager.h"
49#include "BESInfo.h"
50#include "BESStopWatch.h"
51#include "TheBESKeys.h"
52
53#include "BESDebug.h"
54#include "BESLog.h"
55#include "BESSyntaxUserError.h"
56
57#define LOG_ONLY_GET_COMMANDS
58#define MODULE "bes"
59#define prolog std::string("BESXMLInterface::").append(__func__).append("() - ")
60
61BESXMLInterface::BESXMLInterface(const string &xml_doc, ostream *strm) :
62 BESInterface(strm), d_xml_document(xml_doc)
63{
64 // This is needed because we want the parent to have access to the information
65 // added to the DHI
66 d_dhi_ptr = &d_xml_interface_dhi;
67}
68
69BESXMLInterface::~BESXMLInterface()
70{
71 clean();
72}
73
77{
78 BESDEBUG("bes", prolog << "BEGIN" << endl);
79 BESDEBUG("bes", prolog << "Building request plan for xml document: " << endl << d_xml_document << endl);
80
81 // I do not know why, but uncommenting this macro breaks some tests
82 // on Linux but not OSX (CentOS 6, Ubuntu 12 versus OSX 10.11) by
83 // causing some XML elements in DMR responses to be twiddled in the
84 // responses build on Linux but not on OSX.
85 //
86 // LIBXML_TEST_VERSION
87
88 xmlDoc *doc = nullptr;
89 xmlNode *root_element = nullptr;
90 xmlNode *current_node = nullptr;
91
92 try {
93 // set the default error function to my own
94 vector<string> parseerrors;
95 xmlSetGenericErrorFunc((void *) &parseerrors, BESXMLUtils::XMLErrorFunc);
96
97 // XML_PARSE_NONET
98 doc = xmlReadMemory(d_xml_document.c_str(), (int)d_xml_document.size(), "" /* base URL */,
99 nullptr /* encoding */, XML_PARSE_NONET /* xmlParserOption */);
100
101 if (doc == nullptr) {
102 string err = "Problem parsing the request xml document:\n";
103 bool isfirst = true;
104 vector<string>::const_iterator i = parseerrors.begin();
105 vector<string>::const_iterator e = parseerrors.end();
106 for (; i != e; i++) {
107 if (!isfirst && (*i).compare(0, 6, "Entity") == 0) {
108 err += "\n";
109 }
110 err += (*i);
111 isfirst = false;
112 }
113 throw BESSyntaxUserError(err, __FILE__, __LINE__);
114 }
115
116 // get the root element and make sure it exists and is called request
117 root_element = xmlDocGetRootElement(doc);
118 if (!root_element) throw BESSyntaxUserError("There is no root element in the xml document", __FILE__, __LINE__);
119
120 string root_name;
121 string root_val;
122 map<string, string> attributes;
123 BESXMLUtils::GetNodeInfo(root_element, root_name, root_val, attributes);
124 if (root_name != "request")
125 throw BESSyntaxUserError(
126 string("The root element should be a request element, name is ").append((char *) root_element->name),
127 __FILE__, __LINE__);
128
129 if (!root_val.empty())
130 throw BESSyntaxUserError(string("The request element must not contain a value, ").append(root_val),
131 __FILE__, __LINE__);
132
133 // there should be a request id property with one value.
134 string &reqId = attributes[REQUEST_ID];
135 if (reqId.empty()) throw BESSyntaxUserError("The request id value empty", __FILE__, __LINE__);
136
137 d_dhi_ptr->data[REQUEST_ID] = reqId;
138
139 BESDEBUG("besxml", "request id = " << d_dhi_ptr->data[REQUEST_ID] << endl);
140
141 // iterate through the children of the request element. Each child is an
142 // individual command.
143 bool has_response = false; // set to true when a command with a response is found.
144 current_node = root_element->children;
145
146 while (current_node) {
147 if (current_node->type == XML_ELEMENT_NODE) {
148 // given the name of this node we should be able to find a
149 // BESXMLCommand object
150 string node_name = (char *) current_node->name;
151
152 if (node_name == SETCONTAINER_STR) {
153 string name;
154 string value;
155 map<string, string> props;
156 BESXMLUtils::GetNodeInfo(current_node, name, value, props);
157 BESDEBUG(MODULE, prolog << "In " << SETCONTAINER_STR << " element. Value: " << value << endl);
159 }
160
161 // The Command Builder scheme is a kind of factory, but which uses lists and
162 // a static method defined by each child of BESXMLCommand (called CommandBuilder).
163 // These static methods make new instances of the specific commands and, in so
164 // doing, _copy_ the DataHandlerInterface instance using that class' clone() method.
165 // jhrg 11/7/17
166 p_xmlcmd_builder bldr = BESXMLCommand::find_command(node_name);
167 if (!bldr)
168 throw BESSyntaxUserError(string("Unable to find command for ").append(node_name), __FILE__,
169 __LINE__);
170
171 BESXMLCommand *current_cmd = bldr(d_xml_interface_dhi);
172 if (!current_cmd)
173 throw BESInternalError(string("Failed to build command object for ").append(node_name), __FILE__,
174 __LINE__);
175
176 // SPECIAL CASE: Process setContext xml_commands here; do not add to d_xml_cmd_list.
177 if (node_name == SET_CONTEXT_STR) {
178 // TODO Something in there leaks 32 bytes for every SetContext command in a bescmd
179 // xml file. I tried removing the containers on the list of the same name, but that
180 // broke tests. Maybe use shared_ptr<> for that list? Maybe look at how the objects
181 // are managed in the 'else' clause below, because that apparently does not leak.
182 // jhrg 5/11/22
183 current_cmd->parse_request(current_node);
184
185 // Call SetContextsResponseHandler::execute() here not in execute_data_request_plan().
186 //
187 // SetContextsResponseHandler::execute() only calls BESContextManager::set_context(),
188 // and these actions need to occur before execute_data_request_plan().
189 BESDataHandlerInterface &setContext_xml_dhi = current_cmd->get_xmlcmd_dhi();
190 setContext_xml_dhi.response_handler->execute(setContext_xml_dhi);
191
192 // current_cmd is leaked in this case without delete. In the else block below, it
193 // will be deleted by the BESXMLInterface destructor when that iterates through
194 // d_xml_cmd_list. jhrg 5/11/22
195 delete current_cmd;
196 } else {
197 // push this new command to the back of the list
198 d_xml_cmd_list.push_back(current_cmd);
199
200 // only one of the commands in a request can build a response
201 bool cmd_has_response = current_cmd->has_response();
202 if (has_response && cmd_has_response)
203 throw BESSyntaxUserError("Commands with multiple responses not supported.", __FILE__, __LINE__);
204
205 has_response = cmd_has_response;
206
207 // parse the request given the current node
208 current_cmd->parse_request(current_node);
209
210 // Check if the correct transmitter is present. We look for it again in do_transmit()
211 // where it is actually used. This test just keeps us from building a response that
212 // cannot be transmitted. jhrg 11/8/17
213 //
214 // TODO We could add the 'transmitter' to the DHI.
215 BESDataHandlerInterface &current_dhi = current_cmd->get_xmlcmd_dhi();
216
217 string return_as = current_dhi.data[RETURN_CMD];
218 if (!return_as.empty() && !BESReturnManager::TheManager()->find_transmitter(return_as))
219 throw BESSyntaxUserError(string("Unable to find transmitter ").append(return_as), __FILE__,
220 __LINE__);
221 }
222 }
223
224 current_node = current_node->next;
225 }
226 }
227 catch (...) {
228 xmlFreeDoc(doc);
229 xmlCleanupParser();
230 throw;
231 }
232
233 xmlFreeDoc(doc);
234
235 // Removed since the docs indicate it's not needed and it might be
236 // contributing to memory issues flagged by valgrind. 2/25/09 jhrg
237 //
238 // Added this back in. It seems to the the cause of BES-40 - where
239 // When certain tests are run, the order of <Dimension..> elements
240 // in a DMR for a server function result is different when the BESDEBUG
241 // output is on versus when it is not. This was true only when the
242 // BESDEBUG context was 'besxml' or timing,' which lead me here.
243 // Making this call removes the errant behavior. I've run tests using
244 // valgrind and I see no memory problems from this call. jhrg 9/25/15
245 xmlCleanupParser();
246
247 BESDEBUG("bes", "Done building request plan" << endl);
248}
249
254{
255 // In 'verbose' logging mode, log all the commands.
256 VERBOSE(d_dhi_ptr->data[REQUEST_FROM] << " [" << d_dhi_ptr->data[LOG_INFO] << "] executing" << endl);
257
258 // This is the main log entry when the server is not in 'verbose' mode.
259 // There are two ways we can do this, one writes a log line for only the
260 // get commands, the other write the set container, define and get commands.
261 // TODO Make this configurable? jhrg 11/14/17
262#ifdef LOG_ONLY_GET_COMMANDS
263 // Special logging action for the 'get' command. In non-verbose logging mode,
264 // only log the get command.
265 if (d_dhi_ptr->action.find("get.") != string::npos) {
266
267 string log_delim="|&|"; //",";
268
269 string new_log_info;
270
271 // If the OLFS sent its log info, integrate that into the log output
272 bool found = false;
273 string olfs_log_line = BESContextManager::TheManager()->get_context("olfsLog", found);
274 if(found){
275 new_log_info.append("OLFS").append(log_delim).append(olfs_log_line).append(log_delim);
276 new_log_info.append("BES").append(log_delim);
277 }
278
279 new_log_info.append(d_dhi_ptr->action);
280
281 if (!d_dhi_ptr->data[RETURN_CMD].empty())
282 new_log_info.append(log_delim).append(d_dhi_ptr->data[RETURN_CMD]);
283
284 // Assume this is DAP and thus there is at most one container. Log a warning if that's
285 // not true. jhrg 11/14/17
286 BESContainer *c = *(d_dhi_ptr->containers.begin());
287 if (c) {
288 if (!c->get_real_name().empty()) new_log_info.append(log_delim).append(c->get_real_name());
289
290 if (!c->get_constraint().empty()) {
291 new_log_info.append(log_delim).append(c->get_constraint());
292 }
293 else {
294 if (!c->get_dap4_constraint().empty()) new_log_info.append(log_delim).append(c->get_dap4_constraint());
295 if (!c->get_dap4_function().empty()) new_log_info.append(log_delim).append(c->get_dap4_function());
296 }
297 }
298
299 REQUEST_LOG(new_log_info << endl);
300
301 if (d_dhi_ptr->containers.size() > 1)
302 ERROR_LOG("The previous command had multiple containers defined, but only the first was logged.");
303 }
304#else
305 if (!BESLog::TheLog()->is_verbose()) {
306 if (d_dhi_ptr->action.find("set.context") == string::npos
307 && d_dhi_ptr->action.find("show.catalog") == string::npos) {
308 LOG(d_dhi_ptr->data[LOG_INFO] << endl);
309 }
310 }
311#endif
312}
313
317{
318 vector<BESXMLCommand *>::iterator i = d_xml_cmd_list.begin();
319 vector<BESXMLCommand *>::iterator e = d_xml_cmd_list.end();
320 for (; i != e; i++) {
321 (*i)->prep_request();
322
323 d_dhi_ptr = &(*i)->get_xmlcmd_dhi();
324
326
327 // Here's where we could look at the dynamic type to do something different
328 // for a new kind of XMLCommand (e.g., SimpleXMLCommand). for that new command,
329 // move the code now in the response_handler->execute() and ->transmit() into
330 // it. This would eliminate the ResponseHandlers. However, that might not be the
331 // best way to handle the 'get' command, which uses a different ResponseHandler
332 // for each different 'type' of thing it will 'get'. jhrg 3/14/18
333
334 if (!d_dhi_ptr->response_handler)
335 throw BESInternalError(string("The response handler '") + d_dhi_ptr->action + "' does not exist", __FILE__,
336 __LINE__);
337
338 d_dhi_ptr->response_handler->execute(*d_dhi_ptr);
339
340 transmit_data(); // TODO move method body in here? jhrg 11/8/17
341
342 }
343}
344
359{
360 if (d_dhi_ptr->error_info) {
361 VERBOSE(d_dhi_ptr->data[SERVER_PID] << " from " << d_dhi_ptr->data[REQUEST_FROM] << " ["
362 << d_dhi_ptr->data[LOG_INFO] << "] Error" << endl);
363
364 ostringstream strm;
365 d_dhi_ptr->error_info->print(strm);
366 INFO_LOG("Transmitting error content: " << strm.str() << endl);
367
369 }
370 else if (d_dhi_ptr->response_handler) {
371 VERBOSE(d_dhi_ptr->data[REQUEST_FROM] << " [" << d_dhi_ptr->data[LOG_INFO] << "] transmitting" << endl);
372
373 BESStopWatch sw;
374 if (BESDebug::IsSet(TIMING_LOG_KEY)) sw.start(d_dhi_ptr->data[LOG_INFO] + " transmitting", d_dhi_ptr->data[REQUEST_ID]);
375
376 string return_as = d_dhi_ptr->data[RETURN_CMD];
377 if (!return_as.empty()) {
378 d_transmitter = BESReturnManager::TheManager()->find_transmitter(return_as);
379 if (!d_transmitter) {
380 throw BESSyntaxUserError(string("Unable to find transmitter ") + return_as, __FILE__, __LINE__);
381 }
382 }
383
384 d_dhi_ptr->response_handler->transmit(d_transmitter, *d_dhi_ptr);
385 }
386}
387
396{
397 if (BESLog::TheLog()->is_verbose()) {
398 vector<BESXMLCommand *>::iterator i = d_xml_cmd_list.begin();
399 vector<BESXMLCommand *>::iterator e = d_xml_cmd_list.end();
400 for (; i != e; i++) {
401 d_dhi_ptr = &(*i)->get_xmlcmd_dhi();
402
403 // IF the DHI's error_info object pointer is null, the request was successful.
404 string result = (!d_dhi_ptr->error_info) ? "completed" : "failed";
405
406 // This is only printed for verbose logging.
407 VERBOSE(d_dhi_ptr->data[REQUEST_FROM] << " [" << d_dhi_ptr->data[LOG_INFO] << "] " << result << endl);
408 }
409 }
410}
411
415{
416 vector<BESXMLCommand *>::iterator i = d_xml_cmd_list.begin();
417 vector<BESXMLCommand *>::iterator e = d_xml_cmd_list.end();
418 for (; i != e; i++) {
419 BESXMLCommand *cmd = *i;
420 d_dhi_ptr = &cmd->get_xmlcmd_dhi();
421
422 if (d_dhi_ptr) {
423 VERBOSE(d_dhi_ptr->data[REQUEST_FROM] << " [" << d_dhi_ptr->data[LOG_INFO] << "] cleaning" << endl);
424
425 d_dhi_ptr->clean(); // Delete the ResponseHandler if present
426 }
427
428 delete cmd;
429 }
430
431 d_xml_cmd_list.clear();
432}
433
440void BESXMLInterface::dump(ostream &strm) const
441{
442 strm << BESIndent::LMarg << "BESXMLInterface::dump - (" << (void *) this << ")" << endl;
443 BESIndent::Indent();
444 BESInterface::dump(strm);
445 vector<BESXMLCommand *>::const_iterator i = d_xml_cmd_list.begin();
446 vector<BESXMLCommand *>::const_iterator e = d_xml_cmd_list.end();
447 for (; i != e; i++) {
448 BESXMLCommand *cmd = *i;
449 cmd->dump(strm);
450 }
451 BESIndent::UnIndent();
452}
453
A container is something that holds data. E.G., a netcdf file or a database entry.
Definition: BESContainer.h:65
std::string get_dap4_constraint() const
retrieve the constraint expression for this container
Definition: BESContainer.h:203
std::string get_dap4_function() const
retrieve the constraint expression for this container
Definition: BESContainer.h:212
std::string get_real_name() const
retrieve the real name for this container, such as a file name.
Definition: BESContainer.h:180
std::string get_constraint() const
retrieve the constraint expression for this container
Definition: BESContainer.h:194
virtual std::string get_context(const std::string &name, bool &found)
retrieve the value of the specified context from the BES
Structure storing information used by the BES to handle the request.
std::map< std::string, std::string > data
the map of string data that will be required for the current request.
std::string action
the response object requested, e.g. das, dds
void clean()
clean up any information created within this data handler interface
BESInfo * error_info
error information object
static bool IsSet(const std::string &flagName)
see if the debug context flagName is set to true
Definition: BESDebug.h:168
virtual void transmit(BESTransmitter *transmitter, BESDataHandlerInterface &dhi)=0
transmit the informational object
virtual void print(std::ostream &strm)
print the information from this informational object to the specified stream
Definition: BESInfo.cc:261
Entry point into BES, building responses to given requests.
Definition: BESInterface.h:118
BESDataHandlerInterface * d_dhi_ptr
Allocated by the child class.
Definition: BESInterface.h:124
BESTransmitter * d_transmitter
The Transmitter to use for the result.
Definition: BESInterface.h:125
void dump(std::ostream &strm) const override
dumps information about this object
exception thrown if internal error encountered
virtual void execute(BESDataHandlerInterface &dhi)=0
knows how to build a requested response object
virtual void transmit(BESTransmitter *transmitter, BESDataHandlerInterface &dhi)=0
transmit the response object built by the execute command using the specified transmitter object
virtual bool start(std::string name)
Definition: BESStopWatch.cc:67
error thrown if there is a user syntax error in the request or any other user error
Base class for the BES's commands.
Definition: BESXMLCommand.h:63
virtual bool has_response()=0
Does this command return a response to the client?
virtual BESDataHandlerInterface & get_xmlcmd_dhi()
Return the current BESDataHandlerInterface.
virtual void dump(std::ostream &strm) const
dumps information about this object
virtual void parse_request(xmlNode *node)=0
Parse the XML request document beginning at the given node.
static p_xmlcmd_builder find_command(const std::string &cmd_str)
Find the BESXMLCommand creation function with the given name.
void transmit_data() override
Transmit the response object.
void execute_data_request_plan() override
Execute the data request plan.
void clean() override
Clean up after the request is completed.
void log_the_command()
Log information about the command.
void log_status() override
Log the status of the request to the BESLog file.
void build_data_request_plan() override
Build the data request plan using the BESCmdParser.
void dump(std::ostream &strm) const override
dumps information about this object
static void GetNodeInfo(xmlNode *node, std::string &name, std::string &value, std::map< std::string, std::string > &props)
get the name, value if any, and any properties for the specified node
Definition: BESXMLUtils.cc:109
static void XMLErrorFunc(void *context, const char *msg,...)
error function used by libxml2 to report errors
Definition: BESXMLUtils.cc:54
static TheBESKeys * TheKeys()
Definition: TheBESKeys.cc:71
void load_dynamic_config(std::string name)
Loads the the applicable dynamic configuration or nothing if no configuration is applicable.
Definition: TheBESKeys.cc:682