bes  Updated for version 3.20.6
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 using namespace std;
39 
40 #include "BESXMLInterface.h"
41 #include "BESXMLCommand.h"
42 #include "BESXMLUtils.h"
43 #include "BESDataNames.h"
44 #include "BESContextManager.h"
45 
46 #include "BESResponseHandler.h"
47 #include "BESReturnManager.h"
48 #include "BESInfo.h"
49 #include "BESStopWatch.h"
50 
51 #include "BESDebug.h"
52 #include "BESLog.h"
53 #include "BESSyntaxUserError.h"
54 
55 #define LOG_ONLY_GET_COMMANDS
56 
57 BESXMLInterface::BESXMLInterface(const string &xml_doc, ostream *strm) :
58  BESInterface(strm), d_xml_document(xml_doc)
59 {
60  // This is needed because we want the parent to have access to the information
61  // added to the DHI
62  d_dhi_ptr = &d_xml_interface_dhi;
63 }
64 
65 BESXMLInterface::~BESXMLInterface()
66 {
67  clean();
68 }
69 
73 {
74  BESDEBUG("bes", "Entering: " << __PRETTY_FUNCTION__ << endl);
75  BESDEBUG("bes", "building request plan for xml document: " << endl << d_xml_document << endl);
76 
77  // I do not know why, but uncommenting this macro breaks some tests
78  // on Linux but not OSX (CentOS 6, Ubuntu 12 versus OSX 10.11) by
79  // causing some XML elements in DMR responses to be twiddled in the
80  // responses build on Linux but not on OSX.
81  //
82  // LIBXML_TEST_VERSION
83 
84  xmlDoc *doc = NULL;
85  xmlNode *root_element = NULL;
86  xmlNode *current_node = NULL;
87 
88  try {
89  // set the default error function to my own
90  vector<string> parseerrors;
91  xmlSetGenericErrorFunc((void *) &parseerrors, BESXMLUtils::XMLErrorFunc);
92 
93  // XML_PARSE_NONET
94  doc = xmlReadMemory(d_xml_document.c_str(), d_xml_document.size(), "" /* base URL */,
95  NULL /* encoding */, XML_PARSE_NONET /* xmlParserOption */);
96 
97  if (doc == NULL) {
98  string err = "Problem parsing the request xml document:\n";
99  bool isfirst = true;
100  vector<string>::const_iterator i = parseerrors.begin();
101  vector<string>::const_iterator e = parseerrors.end();
102  for (; i != e; i++) {
103  if (!isfirst && (*i).compare(0, 6, "Entity") == 0) {
104  err += "\n";
105  }
106  err += (*i);
107  isfirst = false;
108  }
109  throw BESSyntaxUserError(err, __FILE__, __LINE__);
110  }
111 
112  // get the root element and make sure it exists and is called request
113  root_element = xmlDocGetRootElement(doc);
114  if (!root_element) throw BESSyntaxUserError("There is no root element in the xml document", __FILE__, __LINE__);
115 
116  string root_name;
117  string root_val;
118  map<string, string> attributes;
119  BESXMLUtils::GetNodeInfo(root_element, root_name, root_val, attributes);
120  if (root_name != "request")
121  throw BESSyntaxUserError(
122  string("The root element should be a request element, name is ").append((char *) root_element->name),
123  __FILE__, __LINE__);
124 
125  if (!root_val.empty())
126  throw BESSyntaxUserError(string("The request element must not contain a value, ").append(root_val),
127  __FILE__, __LINE__);
128 
129  // there should be a request id property with one value.
130  string &reqId = attributes[REQUEST_ID];
131  if (reqId.empty()) throw BESSyntaxUserError("The request id value empty", __FILE__, __LINE__);
132 
133  d_dhi_ptr->data[REQUEST_ID] = reqId;
134 
135  BESDEBUG("besxml", "request id = " << d_dhi_ptr->data[REQUEST_ID] << endl);
136 
137  // iterate through the children of the request element. Each child is an
138  // individual command.
139  bool has_response = false; // set to true when a command with a response is found.
140  current_node = root_element->children;
141 
142  while (current_node) {
143  if (current_node->type == XML_ELEMENT_NODE) {
144  // given the name of this node we should be able to find a
145  // BESXMLCommand object
146  string node_name = (char *) current_node->name;
147 
148  // The Command Builder scheme is a kind of factory, but which uses lists and
149  // a static method defined by each child of BESXMLCommand (called CommandBuilder).
150  // These static methods make new instances of the specific commands and, in so
151  // doing, _copy_ the DataHandlerInterface instance using that class' clone() method.
152  // jhrg 11/7/17
153  p_xmlcmd_builder bldr = BESXMLCommand::find_command(node_name);
154  if (!bldr)
155  throw BESSyntaxUserError(string("Unable to find command for ").append(node_name), __FILE__,
156  __LINE__);
157 
158  BESXMLCommand *current_cmd = bldr(d_xml_interface_dhi);
159  if (!current_cmd)
160  throw BESInternalError(string("Failed to build command object for ").append(node_name), __FILE__,
161  __LINE__);
162 
163  // push this new command to the back of the list
164  d_xml_cmd_list.push_back(current_cmd);
165 
166  // only one of the commands in a request can build a response
167  bool cmd_has_response = current_cmd->has_response();
168  if (has_response && cmd_has_response)
169  throw BESSyntaxUserError("Commands with multiple responses not supported.", __FILE__, __LINE__);
170 
171  has_response = cmd_has_response;
172 
173  // parse the request given the current node
174  current_cmd->parse_request(current_node);
175 
176  // Check if the correct transmitter is present. We look for it again in do_transmit()
177  // where it is actually used. This test just keeps us from building a response that
178  // cannot be transmitted. jhrg 11/8/17
179  //
180  // TODO We could add the 'transmitter' to the DHI.
181  BESDataHandlerInterface &current_dhi = current_cmd->get_xmlcmd_dhi();
182 
183  string return_as = current_dhi.data[RETURN_CMD];
184  if (!return_as.empty() && !BESReturnManager::TheManager()->find_transmitter(return_as))
185  throw BESSyntaxUserError(string("Unable to find transmitter ").append(return_as), __FILE__,
186  __LINE__);
187  }
188 
189  current_node = current_node->next;
190  }
191  }
192  catch (...) {
193  xmlFreeDoc(doc);
194  xmlCleanupParser();
195  throw;
196  }
197 
198  xmlFreeDoc(doc);
199 
200  // Removed since the docs indicate it's not needed and it might be
201  // contributing to memory issues flagged by valgrind. 2/25/09 jhrg
202  //
203  // Added this back in. It seems to the the cause of BES-40 - where
204  // When certain tests are run, the order of <Dimension..> elements
205  // in a DMR for a server function result is different when the BESDEBUG
206  // output is on versus when it is not. This was true only when the
207  // BESDEBUG context was 'besxml' or timing,' which lead me here.
208  // Making this call removes the errant behavior. I've run tests using
209  // valgrind and I see no memory problems from this call. jhrg 9/25/15
210  xmlCleanupParser();
211 
212  BESDEBUG("bes", "Done building request plan" << endl);
213 }
214 
218 {
219  vector<BESXMLCommand *>::iterator i = d_xml_cmd_list.begin();
220  vector<BESXMLCommand *>::iterator e = d_xml_cmd_list.end();
221  for (; i != e; i++) {
222  (*i)->prep_request(); // TODO remove this if possible jhrg 1/7/19
223 
224  d_dhi_ptr = &(*i)->get_xmlcmd_dhi();
225 
226  // In 'verbose' logging mode, log all the commands.
227  VERBOSE(d_dhi_ptr->data[REQUEST_FROM] << " [" << d_dhi_ptr->data[LOG_INFO] << "] executing" << endl);
228 
229  // This is the main log entry when the server is not in 'verbose' mode.
230  // There are two ways we can do this, one writes a log line for only the
231  // get commands, the other write the set container, define and get commands.
232  // TODO Make this configurable? jhrg 11/14/17
233 #ifdef LOG_ONLY_GET_COMMANDS
234  // Special logging action for the 'get' command. In non-verbose logging mode,
235  // only log the get command.
236  if (d_dhi_ptr->action.find("get.") != string::npos) {
237 
238  string log_delim="|&|"; //",";
239 
240  string new_log_info = "";
241 
242  // If the OLFS sent it's log info, integrate that into the log output
243  bool found = false;
244  string olfs_log_line = BESContextManager::TheManager()->get_context("olfsLog", found);
245  if(found){
246  new_log_info.append("OLFS").append(log_delim).append(olfs_log_line).append(log_delim);
247  new_log_info.append("BES").append(log_delim);
248  }
249 
250  new_log_info.append(d_dhi_ptr->action);
251 
252 
253  if (!d_dhi_ptr->data[RETURN_CMD].empty())
254  new_log_info.append(log_delim).append(d_dhi_ptr->data[RETURN_CMD]);
255 
256  // Assume this is DAP and thus there is at most one container. Log a warning if that's
257  // not true. jhrg 11/14/17
258  BESContainer *c = *(d_dhi_ptr->containers.begin());
259  if (c) {
260  if (!c->get_real_name().empty()) new_log_info.append(log_delim).append(c->get_real_name());
261 
262  if (!c->get_constraint().empty()) {
263  new_log_info.append(log_delim).append(c->get_constraint());
264  }
265  else {
266  if (!c->get_dap4_constraint().empty()) new_log_info.append(log_delim).append(c->get_dap4_constraint());
267  if (!c->get_dap4_function().empty()) new_log_info.append(log_delim).append(c->get_dap4_function());
268  }
269  }
270 
271  LOG(new_log_info << endl);
272 
273  if (d_dhi_ptr->containers.size() > 1)
274  LOG("Warning: The previous command had multiple containers defined, but only the was logged.");
275  }
276 #else
277  if (!BESLog::TheLog()->is_verbose()) {
278  if (d_dhi_ptr->action.find("set.context") == string::npos
279  && d_dhi_ptr->action.find("show.catalog") == string::npos) {
280  LOG(d_dhi_ptr->data[LOG_INFO] << endl);
281  }
282  }
283 #endif
284 
286 
287  // Here's where we could look at the dynamic type to do something different
288  // for a new kind of XMLCommand (e.g., SimpleXMLCommand). for that new command,
289  // move the code now in the response_handler->execute() and ->transmit() into
290  // it. This would eliminate the ResponseHandlers. However, that might not be the
291  // best way to handle the 'get' command, which uses a different ResponseHandler
292  // for each different 'type' of thing it will 'get'. jhrg 3/14/18
293 
295 
296  if (!d_dhi_ptr->response_handler)
297  throw BESInternalError(string("The response handler '") + d_dhi_ptr->action + "' does not exist", __FILE__,
298  __LINE__);
299 
300  d_dhi_ptr->response_handler->execute(*d_dhi_ptr);
301 
302  transmit_data(); // TODO move method body in here? jhrg 11/8/17
303  }
304 }
305 
320 {
321  if (d_dhi_ptr->error_info) {
322  VERBOSE(d_dhi_ptr->data[SERVER_PID] << " from " << d_dhi_ptr->data[REQUEST_FROM] << " ["
323  << d_dhi_ptr->data[LOG_INFO] << "] Error" << endl);
324 
325  ostringstream strm;
326  d_dhi_ptr->error_info->print(strm);
327  LOG("Transmitting error: " << strm.str() << endl);
328 
330  }
331  else if (d_dhi_ptr->response_handler) {
332  VERBOSE(d_dhi_ptr->data[REQUEST_FROM] << " [" << d_dhi_ptr->data[LOG_INFO] << "] transmitting" << endl);
333 
334  BESStopWatch sw;
335  if (BESISDEBUG(TIMING_LOG)) sw.start(d_dhi_ptr->data[LOG_INFO] + " transmitting", d_dhi_ptr->data[REQUEST_ID]);
336 
337  string return_as = d_dhi_ptr->data[RETURN_CMD];
338  if (!return_as.empty()) {
339  d_transmitter = BESReturnManager::TheManager()->find_transmitter(return_as);
340  if (!d_transmitter) {
341  throw BESSyntaxUserError(string("Unable to find transmitter ") + return_as, __FILE__, __LINE__);
342  }
343  }
344 
345  d_dhi_ptr->response_handler->transmit(d_transmitter, *d_dhi_ptr);
346  }
347 }
348 
357 {
358  if (BESLog::TheLog()->is_verbose()) {
359  vector<BESXMLCommand *>::iterator i = d_xml_cmd_list.begin();
360  vector<BESXMLCommand *>::iterator e = d_xml_cmd_list.end();
361  for (; i != e; i++) {
362  d_dhi_ptr = &(*i)->get_xmlcmd_dhi();
363 
364  // IF the DHI's error_info object pointer is null, the request was successful.
365  string result = (!d_dhi_ptr->error_info) ? "completed" : "failed";
366 
367  // This is only printed for verbose logging.
368  LOG(d_dhi_ptr->data[REQUEST_FROM] << " [" << d_dhi_ptr->data[LOG_INFO] << "] " << result << endl);
369  }
370  }
371 }
372 
376 {
377  vector<BESXMLCommand *>::iterator i = d_xml_cmd_list.begin();
378  vector<BESXMLCommand *>::iterator e = d_xml_cmd_list.end();
379  for (; i != e; i++) {
380  BESXMLCommand *cmd = *i;
381  d_dhi_ptr = &cmd->get_xmlcmd_dhi();
382 
383  if (d_dhi_ptr) {
384  VERBOSE(d_dhi_ptr->data[REQUEST_FROM] << " [" << d_dhi_ptr->data[LOG_INFO] << "] cleaning" << endl);
385 
386  d_dhi_ptr->clean(); // Delete the ResponseHandler if present
387  }
388 
389  delete cmd;
390  }
391 
392  d_xml_cmd_list.clear();
393 }
394 
401 void BESXMLInterface::dump(ostream &strm) const
402 {
403  strm << BESIndent::LMarg << "BESXMLInterface::dump - (" << (void *) this << ")" << endl;
404  BESIndent::Indent();
405  BESInterface::dump(strm);
406  vector<BESXMLCommand *>::const_iterator i = d_xml_cmd_list.begin();
407  vector<BESXMLCommand *>::const_iterator e = d_xml_cmd_list.end();
408  for (; i != e; i++) {
409  BESXMLCommand *cmd = *i;
410  cmd->dump(strm);
411  }
412  BESIndent::UnIndent();
413 }
414 
BESXMLInterface::log_status
virtual void log_status()
Log the status of the request to the BESLog file.
Definition: BESXMLInterface.cc:356
BESStopWatch::start
virtual bool start(std::string name)
Definition: BESStopWatch.cc:58
BESDataHandlerInterface::action
std::string action
the response object requested, e.g. das, dds
Definition: BESDataHandlerInterface.h:79
BESContainer::get_dap4_function
std::string get_dap4_function() const
retrieve the constraint expression for this container
Definition: BESContainer.h:212
BESDataHandlerInterface::clean
void clean()
clean up any information created within this data handler interface
Definition: BESDataHandlerInterface.cc:134
BESInterface
Entry point into BES, building responses to given requests.
Definition: BESInterface.h:118
BESInfo::print
virtual void print(std::ostream &strm)
print the information from this informational object to the specified stream
Definition: BESInfo.cc:261
BESXMLUtils::GetNodeInfo
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:105
BESInfo::transmit
virtual void transmit(BESTransmitter *transmitter, BESDataHandlerInterface &dhi)=0
transmit the informational object
BESXMLCommand::parse_request
virtual void parse_request(xmlNode *node)=0
Parse the XML request document beginning at the given node.
BESContainer::get_constraint
std::string get_constraint() const
retrieve the constraint expression for this container
Definition: BESContainer.h:194
BESXMLCommand::dump
virtual void dump(std::ostream &strm) const
dumps information about this object
Definition: BESXMLCommand.cc:119
BESXMLUtils::XMLErrorFunc
static void XMLErrorFunc(void *context, const char *msg,...)
error function used by libxml2 to report errors
Definition: BESXMLUtils.cc:50
BESSyntaxUserError
error thrown if there is a user syntax error in the request or any other user error
Definition: BESSyntaxUserError.h:41
BESXMLCommand::get_xmlcmd_dhi
virtual BESDataHandlerInterface & get_xmlcmd_dhi()
Return the current BESDataHandlerInterface.
Definition: BESXMLCommand.h:120
BESDataHandlerInterface::data
std::map< std::string, std::string > data
the map of string data that will be required for the current request.
Definition: BESDataHandlerInterface.h:90
BESXMLCommand
Base class for the BES's commands.
Definition: BESXMLCommand.h:63
BESResponseHandler::transmit
virtual void transmit(BESTransmitter *transmitter, BESDataHandlerInterface &dhi)=0
transmit the response object built by the execute command using the specified transmitter object
BESInternalError
exception thrown if internal error encountered
Definition: BESInternalError.h:43
BESStopWatch
Definition: BESStopWatch.h:55
BESInterface::dump
virtual void dump(std::ostream &strm) const
dumps information about this object
Definition: BESInterface.cc:629
BESXMLCommand::has_response
virtual bool has_response()=0
Does this command return a response to the client?
BESXMLInterface::transmit_data
virtual void transmit_data()
Transmit the response object.
Definition: BESXMLInterface.cc:319
BESXMLCommand::find_command
static p_xmlcmd_builder find_command(const std::string &cmd_str)
Find the BESXMLCommand creation function with the given name.
Definition: BESXMLCommand.cc:108
BESContainer
A container is something that holds data. E.G., a netcdf file or a database entry.
Definition: BESContainer.h:65
BESContainer::get_real_name
std::string get_real_name() const
retrieve the real name for this container, such as a file name.
Definition: BESContainer.h:180
BESInterface::d_transmitter
BESTransmitter * d_transmitter
The Transmitter to use for the result.
Definition: BESInterface.h:125
BESResponseHandler::execute
virtual void execute(BESDataHandlerInterface &dhi)=0
knows how to build a requested response object
BESInterface::d_dhi_ptr
BESDataHandlerInterface * d_dhi_ptr
Allocated by the child class.
Definition: BESInterface.h:124
BESXMLInterface::build_data_request_plan
virtual void build_data_request_plan()
Build the data request plan using the BESCmdParser.
Definition: BESXMLInterface.cc:72
BESContextManager::get_context
virtual std::string get_context(const std::string &name, bool &found)
retrieve the value of the specified context from the BES
Definition: BESContextManager.cc:77
BESXMLInterface::execute_data_request_plan
virtual void execute_data_request_plan()
Execute the data request plan.
Definition: BESXMLInterface.cc:217
BESDataHandlerInterface
Structure storing information used by the BES to handle the request.
Definition: BESDataHandlerInterface.h:56
BESDataHandlerInterface::error_info
BESInfo * error_info
error information object
Definition: BESDataHandlerInterface.h:94
BESXMLInterface::dump
virtual void dump(std::ostream &strm) const
dumps information about this object
Definition: BESXMLInterface.cc:401
BESContainer::get_dap4_constraint
std::string get_dap4_constraint() const
retrieve the constraint expression for this container
Definition: BESContainer.h:203
BESXMLInterface::clean
virtual void clean()
Clean up after the request is completed.
Definition: BESXMLInterface.cc:375