bes Updated for version 3.21.1
The Backend Server (BES) is the lower two tiers of the Hyrax data server
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
38#include "BESXMLInterface.h"
39#include <BESUtil.h>
40#include "BESXMLCommand.h"
41#include "BESXMLUtils.h"
42#include "BESDataNames.h"
43#include "BESResponseNames.h"
44#include "BESContextManager.h"
45
46#include "BESReturnManager.h"
47#include "BESInfo.h"
48#include "BESStopWatch.h"
49#include "TheBESKeys.h"
50
51#include "BESDebug.h"
52#include "BESLog.h"
53#include "BESSyntaxUserError.h"
54#include "RequestServiceTimer.h"
55
56using namespace std;
57
58#define LOG_ONLY_GET_COMMANDS
59const auto MODULE ="bes";
60const auto BES_XML = "besxml";
61#define prolog string("BESXMLInterface::").append(__func__).append("() - ")
62
63BESXMLInterface::BESXMLInterface(const string &xml_doc, ostream *strm) :
64 BESInterface(strm), d_xml_document(xml_doc)
65{
66 // This is needed because we want the parent to have access to the information
67 // added to the DHI
68 d_dhi_ptr = &d_xml_interface_dhi;
69}
70
71BESXMLInterface::~BESXMLInterface()
72{
73 clean();
74}
75
79{
80 BESDEBUG(BES_XML, prolog << "BEGIN #####################################################" << endl);
81 BESDEBUG(BES_XML, prolog << "Building request plan for xml document: " << endl << d_xml_document << endl);
82
83 // I do not know why, but uncommenting this macro breaks some tests
84 // on Linux but not OSX (CentOS 6, Ubuntu 12 versus OSX 10.11) by
85 // causing some XML elements in DMR responses to be twiddled in the
86 // responses build on Linux but not on OSX.
87 //
88 // LIBXML_TEST_VERSION
89
90 xmlDoc *doc = nullptr;
91 xmlNode *root_element = nullptr;
92 xmlNode *current_node = nullptr;
93
94 try {
95 // set the default error function to my own
96 vector<string> parseerrors;
97 xmlSetGenericErrorFunc((void *) &parseerrors, BESXMLUtils::XMLErrorFunc);
98
99 // XML_PARSE_NONET
100 doc = xmlReadMemory(d_xml_document.c_str(), (int) d_xml_document.size(), "" /* base URL */,
101 nullptr /* encoding */, XML_PARSE_NONET /* xmlParserOption */);
102
103 if (doc == nullptr) {
104 string err = "Problem parsing the request xml document:\n";
105 bool isfirst = true;
106 for (const auto &parseerror: parseerrors) {
107 if (!isfirst && parseerror.compare(0, 6, "Entity") == 0) {
108 err += "\n";
109 }
110 err += parseerror;
111 isfirst = false;
112 }
113 throw BESSyntaxUserError(err, __FILE__, __LINE__);
114 }
115 // get the root element and make sure it exists and is called request
116 root_element = xmlDocGetRootElement(doc);
117 if (!root_element) throw BESSyntaxUserError("There is no root element in the xml document", __FILE__, __LINE__);
118
119 string root_name;
120 string root_val;
121 map<string, string> attributes;
122 BESXMLUtils::GetNodeInfo(root_element, root_name, root_val, attributes);
123 if (root_name != "request")
124 throw BESSyntaxUserError(
125 string("The root element should be a request element, name is ").append(
126 (const 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 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
134 // Ingest request id. We know that we have to forward the request id value to downstream services.
135 // But there is nothing to prevent users/clients from submitting multiple requests with the same request id.
136 // We add the UUID part below so that in our logs we can easily disambiguate duplicate request ids
137 // without breaking the actual request id value needed by downstream services. ndp 03/17/25
138
139 // there should be a request id property with one value.
140 auto reqID = attributes[REQUEST_ID_KEY];
141 BESDEBUG(BES_XML, prolog << "attributes[" << REQUEST_ID_KEY << "]: '" << reqID << "'\n");
142 if (reqID.empty()) {
143 // But if no id then we punt and keep going.
144 reqID = prolog + "NoRequestIdDetected";
145 }
146 BESDEBUG(BES_XML, prolog << "Using reqId: " << reqID << endl);
147
148 d_dhi_ptr->data[REQUEST_ID_KEY] = reqID;
149 BESDEBUG(BES_XML, prolog << "d_dhi_ptr->data[\"" << REQUEST_ID_KEY << "\"]: '" << d_dhi_ptr->data[REQUEST_ID_KEY] << "'\n");
150
151 // there should be a request uuid property with one value.
152 auto reqUUID = attributes[REQUEST_UUID_KEY];
153 BESDEBUG(BES_XML, prolog << "attributes[" << REQUEST_UUID_KEY << "]: '" << reqUUID << "'\n");
154 if (reqUUID.empty()) {
155 // But if no uuid is found then make one.
156 reqUUID = "BesXmlInterface-" + BESUtil::uuid();
157 }
158 BESDEBUG(BES_XML, prolog << "Using reqUUID: " << reqUUID << endl);
159
160 d_dhi_ptr->data[REQUEST_UUID_KEY] = reqUUID;
161 BESDEBUG(BES_XML, prolog << "d_dhi_ptr->data[\"" << REQUEST_UUID_KEY << "\"]: '" << d_dhi_ptr->data[REQUEST_UUID_KEY] << "'\n");
162
163 // Make the request id staring value for the BES application log.
164 auto request_id_for_log = reqID + "-" + reqUUID;
165 BESDEBUG(BES_XML, prolog << "request_id_for_log: '" << request_id_for_log << "'\n");
166
167 BESLog::TheLog()->set_request_id(request_id_for_log);
168 BESDEBUG(BES_XML, prolog << "BESLog::TheLog()->get_request_id(): '" << BESLog::TheLog()->get_request_id() << "'\n");
169
170 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
171
172 auto bes_client_id = attributes[BES_CLIENT_ID_KEY];
173 BESDEBUG(BES_XML, prolog << BES_CLIENT_ID_KEY << ": " << bes_client_id << endl);
174
175 // iterate through the children of the request element. Each child is an
176 // individual command.
177 bool has_response = false; // set to true when a command with a response is found.
178 current_node = root_element->children;
179
180 while (current_node) {
181 if (current_node->type == XML_ELEMENT_NODE) {
182 // given the name of this node we should be able to find a
183 // BESXMLCommand object
184 string node_name = (char *) current_node->name;
185
186 if (node_name == SETCONTAINER_STR) {
187 string name;
188 string value;
189 map<string, string> props;
190 BESXMLUtils::GetNodeInfo(current_node, name, value, props);
191 BESDEBUG(MODULE, prolog << "In " << SETCONTAINER_STR << " element. Value: " << value << endl);
193 }
194
195 // The Command Builder scheme is a kind of factory, but which uses lists and
196 // a static method defined by each child of BESXMLCommand (called CommandBuilder).
197 // These static methods make new instances of the specific commands and, in so
198 // doing, _copy_ the DataHandlerInterface instance using that class' clone() method.
199 // jhrg 11/7/17
200 p_xmlcmd_builder bldr = BESXMLCommand::find_command(node_name);
201 if (!bldr)
202 throw BESSyntaxUserError(string("Unable to find command for ").append(node_name), __FILE__,
203 __LINE__);
204
205 BESXMLCommand *current_cmd = bldr(d_xml_interface_dhi);
206 if (!current_cmd)
207 throw BESInternalError(string("Failed to build command object for ").append(node_name), __FILE__,
208 __LINE__);
209
210 // SPECIAL CASE: Process setContext xml_commands here; do not add to d_xml_cmd_list.
211 if (node_name == SET_CONTEXT_STR) {
212 // TODO Something in there leaks 32 bytes for every SetContext command in a bescmd
213 // xml file. I tried removing the containers on the list of the same name, but that
214 // broke tests. Maybe use shared_ptr<> for that list? Maybe look at how the objects
215 // are managed in the 'else' clause below, because that apparently does not leak.
216 // jhrg 5/11/22
217 current_cmd->parse_request(current_node);
218
219 // Call SetContextsResponseHandler::execute() here not in execute_data_request_plan().
220 //
221 // SetContextsResponseHandler::execute() only calls BESContextManager::set_context(),
222 // and these actions need to occur before execute_data_request_plan().
223 BESDataHandlerInterface &setContext_xml_dhi = current_cmd->get_xmlcmd_dhi();
224 setContext_xml_dhi.response_handler->execute(setContext_xml_dhi);
225
226 // current_cmd is leaked in this case without delete. In the else block below, it
227 // will be deleted by the BESXMLInterface destructor when that iterates through
228 // d_xml_cmd_list. jhrg 5/11/22
229 delete current_cmd;
230 }
231 else {
232 // push this new command to the back of the list
233 d_xml_cmd_list.push_back(current_cmd);
234
235 // only one of the commands in a request can build a response
236 bool cmd_has_response = current_cmd->has_response();
237 if (has_response && cmd_has_response)
238 throw BESSyntaxUserError("Commands with multiple responses not supported.", __FILE__, __LINE__);
239
240 has_response = cmd_has_response;
241
242 // parse the request given the current node
243 current_cmd->parse_request(current_node);
244
245 // Check if the correct transmitter is present. We look for it again in do_transmit()
246 // where it is actually used. This test just keeps us from building a response that
247 // cannot be transmitted. jhrg 11/8/17
248 //
249 // TODO We could add the 'transmitter' to the DHI.
250 BESDataHandlerInterface &current_dhi = current_cmd->get_xmlcmd_dhi();
251
252 string return_as = current_dhi.data[RETURN_CMD];
253 if (!return_as.empty() && !BESReturnManager::TheManager()->find_transmitter(return_as))
254 throw BESSyntaxUserError(string("Unable to find transmitter ").append(return_as), __FILE__,
255 __LINE__);
256 }
257 }
258
259 current_node = current_node->next;
260 }
261 }
262 catch (...) {
263 xmlFreeDoc(doc);
264 xmlCleanupParser();
265 throw;
266 }
267
268 xmlFreeDoc(doc);
269
270 // Removed since the docs indicate it's not needed and it might be
271 // contributing to memory issues flagged by valgrind. 2/25/09 jhrg
272 //
273 // Added this back in. It seems to the the cause of BES-40 - where
274 // When certain tests are run, the order of <Dimension..> elements
275 // in a DMR for a server function result is different when the BESDEBUG
276 // output is on versus when it is not. This was true only when the
277 // BESDEBUG context was 'besxml' or timing,' which lead me here.
278 // Making this call removes the errant behavior. I've run tests using
279 // valgrind and I see no memory problems from this call. jhrg 9/25/15
280 xmlCleanupParser();
281
282 BESDEBUG("bes", "Done building request plan" << endl);
283}
284
289{
290 // In 'verbose' logging mode, log all the commands.
291 VERBOSE(d_dhi_ptr->data[REQUEST_FROM] + " [" + d_dhi_ptr->data[LOG_INFO] + "] executing");
292
293 // This is the main log entry when the server is not in 'verbose' mode.
294 // There are two ways we can do this, one writes a log line for only the
295 // get commands, the other write the set container, define and get commands.
296 // TODO Make this configurable? jhrg 11/14/17
297#ifdef LOG_ONLY_GET_COMMANDS
298 // Special logging action for the 'get' command. In non-verbose logging mode,
299 // only log the get command.
300 if (d_dhi_ptr->action.find("get.") != string::npos) {
301
302 string log_delim="|&|"; //",";
303
304 string new_log_info;
305
306 // If the OLFS sent its log info, integrate that into the log output
307 bool found = false;
308 string olfs_log_line = BESContextManager::TheManager()->get_context("olfsLog", found);
309 if(found){
310 new_log_info.append("OLFS").append(log_delim).append(olfs_log_line).append(log_delim);
311 new_log_info.append("BES").append(log_delim);
312 }
313
314 new_log_info.append(d_dhi_ptr->action);
315
316 if (!d_dhi_ptr->data[RETURN_CMD].empty())
317 new_log_info.append(log_delim).append(d_dhi_ptr->data[RETURN_CMD]);
318
319 // Assume this is DAP and thus there is at most one container. Log a warning if that's
320 // not true. jhrg 11/14/17
321 auto const *c = *(d_dhi_ptr->containers.begin());
322 if (c) {
323 if (!c->get_real_name().empty()) new_log_info.append(log_delim).append(c->get_real_name());
324
325 if (!c->get_constraint().empty()) {
326 new_log_info.append(log_delim).append(c->get_constraint());
327 }
328 else {
329 if (!c->get_dap4_constraint().empty()) new_log_info.append(log_delim).append(c->get_dap4_constraint());
330 if (!c->get_dap4_function().empty()) new_log_info.append(log_delim).append(c->get_dap4_function());
331 }
332 }
333
334 REQUEST_LOG(new_log_info);
335
336 if (d_dhi_ptr->containers.size() > 1)
337 ERROR_LOG("The previous command had multiple containers defined, but only the first was logged.");
338 }
339#else
340 if (!BESLog::TheLog()->is_verbose()) {
341 if (d_dhi_ptr->action.find("set.context") == string::npos
342 && d_dhi_ptr->action.find("show.catalog") == string::npos) {
343 LOG(d_dhi_ptr->data[LOG_INFO] << endl);
344 }
345 }
346#endif
347}
348
352{
353 BES_COMMAND_TIMING(prolog, d_dhi_ptr);
354
355 for(auto bescmd : d_xml_cmd_list){
356 bescmd->prep_request();
357
358 d_dhi_ptr = &bescmd->get_xmlcmd_dhi();
359
361
362 // Here's where we could look at the dynamic type to do something different
363 // for a new kind of XMLCommand (e.g., SimpleXMLCommand). for that new command,
364 // move the code now in the response_handler->execute() and ->transmit() into
365 // it. This would eliminate the ResponseHandlers. However, that might not be the
366 // best way to handle the 'get' command, which uses a different ResponseHandler
367 // for each different 'type' of thing it will 'get'. jhrg 3/14/18
368
369 if (!d_dhi_ptr->response_handler)
370 throw BESInternalError(string("The response handler '") + d_dhi_ptr->action + "' does not exist", __FILE__,
371 __LINE__);
372
373 d_dhi_ptr->response_handler->execute(*d_dhi_ptr);
374
376 prolog + "The BES ran out of time before the data could be transmitted.",
377 __FILE__,__LINE__);
378
380 }
381}
382
397{
398 BES_COMMAND_TIMING(prolog, d_dhi_ptr);
399
400 if (d_dhi_ptr->error_info) {
401 VERBOSE(d_dhi_ptr->data[SERVER_PID] + " from " + d_dhi_ptr->data[REQUEST_FROM] + " ["
402 + d_dhi_ptr->data[LOG_INFO] + "] Error" );
403
404 ostringstream strm;
405 d_dhi_ptr->error_info->print(strm);
406 INFO_LOG("Transmitting error content: " + strm.str() );
407
408 d_dhi_ptr->error_info->transmit(d_transmitter, *d_dhi_ptr);
409 }
410 else if (d_dhi_ptr->response_handler) {
411 VERBOSE(d_dhi_ptr->data[REQUEST_FROM] + " [" + d_dhi_ptr->data[LOG_INFO] + "] transmitting" );
412
413 BES_STOPWATCH_START_DHI(MODULE, prolog + "Elapsed Time To Transmit", d_dhi_ptr);
414
415 string return_as = d_dhi_ptr->data[RETURN_CMD];
416 if (!return_as.empty()) {
417 d_transmitter = BESReturnManager::TheManager()->find_transmitter(return_as);
418 if (!d_transmitter) {
419 throw BESSyntaxUserError(string("Unable to find transmitter ") + return_as, __FILE__, __LINE__);
420 }
421 }
422
423 d_dhi_ptr->response_handler->transmit(d_transmitter, *d_dhi_ptr);
424 }
425}
426
435{
436 if (BESLog::TheLog()->is_verbose()) {
437 for (auto &cmd : d_xml_cmd_list) {
438 d_dhi_ptr = &cmd->get_xmlcmd_dhi();
439
440 // IF the DHI's error_info object pointer is null, the request was successful.
441 string result = (!d_dhi_ptr->error_info) ? "completed" : "failed";
442
443 // This is only printed for verbose logging.
444 VERBOSE(d_dhi_ptr->data[REQUEST_FROM] + " [" + d_dhi_ptr->data[LOG_INFO] + "] " + result );
445 }
446 }
447}
448
451{
452 for (auto *cmd : d_xml_cmd_list) {
453 d_dhi_ptr = &cmd->get_xmlcmd_dhi();
454
455 if (d_dhi_ptr) {
456 VERBOSE(d_dhi_ptr->data[REQUEST_FROM] + " [" + d_dhi_ptr->data[LOG_INFO] + "] cleaning" );
457
458 d_dhi_ptr->clean(); // Delete the ResponseHandler if present
459 }
460
461 delete cmd;
462 }
463
464 d_xml_cmd_list.clear();
465}
466
472void BESXMLInterface::dump(ostream &strm) const
473{
474 strm << BESIndent::LMarg << "BESXMLInterface::dump - (" << static_cast<const void *>(this) << ")" << endl;
475 BESIndent::Indent();
476 BESInterface::dump(strm);
477 for (const auto &cmd : d_xml_cmd_list) {
478 cmd->dump(strm);
479 }
480 BESIndent::UnIndent();
481}
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.
Entry point into BES, building responses to given requests.
BESDataHandlerInterface * d_dhi_ptr
Allocated by the child class.
BESTransmitter * d_transmitter
The Transmitter to use for the result.
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
error thrown if there is a user syntax error in the request or any other user error
Base class for the BES's commands.
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 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
static void XMLErrorFunc(void *context, const char *msg,...)
error function used by libxml2 to report errors
static RequestServiceTimer * TheTimer()
Return a pointer to a singleton timer instance. If an instance does not exist it will create and init...
void throw_if_timeout_expired(const std::string &message, const std::string &file, const int line)
Checks the RequestServiceTimer to determine if the time spent servicing the request at this point has...
static TheBESKeys * TheKeys()
Access to the singleton.
Definition TheBESKeys.cc:85
void load_dynamic_config(const std::string &name)
Loads the the applicable dynamic configuration or nothing if no configuration is applicable.