1 // -*- mode: c++; c-basic-offset:4 -*-
2 
3 // This file is part of libdap, A C++ implementation of the OPeNDAP Data
4 // Access Protocol.
5 
6 // Copyright (c) 2002,2003,2013 OPeNDAP, Inc.
7 // Author: James Gallagher <jgallagher@opendap.org>
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22 //
23 // You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
24 
25 // Test the DMR parser
26 
27 #include "config.h"
28 
29 #include <stdint.h>
30 #include <cstring>
31 
32 #include <fstream>
33 #include <memory>
34 
35 #include "crc.h"
36 
37 #include <GetOpt.h>
38 
39 #include "BaseType.h"
40 #include "Array.h"
41 #include "D4Enum.h"
42 
43 #include "DMR.h"
44 #include "D4Group.h"
45 #include "D4StreamUnMarshaller.h"
46 #include "chunked_ostream.h"
47 #include "chunked_istream.h"
48 
49 #include "util.h"
50 #include "InternalErr.h"
51 #include "Error.h"
52 
53 #include "D4ResponseBuilder.h"
54 #include "ConstraintEvaluator.h"
55 
56 #include "D4ParserSax2.h"
57 #include "D4TestTypeFactory.h"
58 #include "TestCommon.h"
59 
60 #include "D4ConstraintEvaluator.h"
61 #include "D4FunctionEvaluator.h"
62 #include "ServerFunctionsList.h"
63 #include "D4TestFunction.h"
64 #include "D4RValue.h"
65 
66 #include "util.h"
67 #include "mime_util.h"
68 #include "debug.h"
69 
70 int test_variable_sleep_interval = 0;   // Used in Test* classes for testing timeouts.
71 
72 using namespace libdap;
73 
74 /**
75  * Open the named XML file and parse it, assuming that it contains a DMR.
76  * @param name The name of the DMR XML file (or '-' for stdin)
77  * @param debug True if the debug mode of the parse should be used
78  * @param print Once parsed, should the DMR object be printed?
79  * @return true if the parse worked, false otherwise
80  */
81 DMR *
test_dap4_parser(const string & name,bool debug,bool print)82 test_dap4_parser(const string &name, bool debug, bool print)
83 {
84     D4TestTypeFactory *factory = new D4TestTypeFactory;
85     DMR *dataset = new DMR(factory, path_to_filename(name));
86 
87     try {
88         D4ParserSax2 parser;
89         if (name == "-") {
90             parser.intern(cin, dataset, debug);
91         }
92         else {
93             fstream in(name.c_str(), ios_base::in);
94             parser.intern(in, dataset, debug);
95         }
96     }
97     catch(...) {
98         delete factory;
99         delete dataset;
100         throw;
101     }
102 
103     cout << "Parse successful" << endl;
104 
105     if (print) {
106         XMLWriter xml("    ");
107         dataset->print_dap4(xml, false);
108         cout << xml.get_doc() << endl;
109     }
110 
111     delete factory;
112     dataset->set_factory(0);
113     return dataset;
114 }
115 
116 /**
117  * Should the changing values - meant to mimic the DTS - be used?
118  * @param dmr Set for this DMR
119  * @param state True to use the DTS-like values, false otherwise
120  */
121 void
set_series_values(DMR * dmr,bool state)122 set_series_values(DMR *dmr, bool state)
123 {
124 	if (state == true)
125 		dmr->root()->set_read_p(false);
126 
127 	TestCommon *tc = dynamic_cast<TestCommon*>(dmr->root());
128 	if (tc)
129 		tc->set_series_values(state);
130 	else
131 		cerr << "Could not cast root group to TestCommon (" << dmr->root()->type_name() << ", " << dmr->root()->name() << ")" << endl;
132 }
133 
134 /**
135  * Call the parser and then serialize the resulting DMR after applying the
136  * constraint. The persistent representation is written to a file. The file
137  * is name '<name>_data.bin'.
138  *
139  * @param dataset
140  * @param constraint
141  * @param series_values
142  * @return The name of the file that hods the response.
143  */
144 string
send_data(DMR * dataset,const string & constraint,const string & function,bool series_values,bool ce_parser_debug)145 send_data(DMR *dataset, const string &constraint, const string &function, bool series_values, bool ce_parser_debug)
146 {
147     set_series_values(dataset, series_values);
148 
149     // This will be used by the DMR that holds the results of running the functions.
150     // It's declared at this scope because we (may) need it for the code beyond the
151     // function parse/eval code that immediately follows. jhrg 3/12/14
152     D4TestTypeFactory d4_factory;
153     auto_ptr<DMR> function_result(new DMR(&d4_factory, "function_results"));
154 
155 	// The Function Parser
156 	if (!function.empty()) {
157 		ServerFunctionsList *sf_list = ServerFunctionsList::TheList();
158 		ServerFunction *scale = new D4TestFunction;
159 		sf_list->add_function(scale);
160 
161 		D4FunctionEvaluator parser(dataset, sf_list);
162 		if (ce_parser_debug) parser.set_trace_parsing(true);
163 		bool parse_ok = parser.parse(function);
164 		if (!parse_ok)
165 			Error("Function Expression failed to parse.");
166 		else {
167 			if (ce_parser_debug) cerr << "Function Parse OK" << endl;
168 
169 			parser.eval(function_result.get());
170 
171 			// Now use the results of running the functions for the remainder of the
172 			// send_data operation.
173 			dataset = function_result.release();
174 		}
175 	}
176 
177     D4ResponseBuilder rb;
178     rb.set_dataset_name(dataset->name());
179 
180     string file_name = dataset->name() + "_data.bin";
181     ofstream out(file_name.c_str(), ios::out|ios::trunc|ios::binary);
182 
183 	if (!constraint.empty()) {
184 		D4ConstraintEvaluator parser(dataset);
185 		if (ce_parser_debug)
186 		    parser.set_trace_parsing(true);
187 		bool parse_ok = parser.parse(constraint);
188 		if (!parse_ok)
189 			throw Error("Constraint Expression failed to parse.");
190 		else if (ce_parser_debug)
191 		    cerr << "CE Parse OK" << endl;
192 	}
193     else {
194     	dataset->root()->set_send_p(true);
195     }
196 
197     rb.send_dap(out, *dataset, /*with mime headers*/ true, !constraint.empty());
198     out.close();
199 
200     return file_name;
201 }
202 
203 void
intern_data(DMR * dataset,bool series_values)204 intern_data(DMR *dataset, /*const string &constraint,*/ bool series_values)
205 {
206     set_series_values(dataset, series_values);
207 
208     // Mark all variables to be sent in their entirety. No CEs are used
209     // when 'interning' variables' data.
210     dataset->root()->set_send_p(true);
211 #if 0
212     Crc32 checksum;
213 #endif
214     dataset->root()->intern_data(/*checksum, *dataset, eval*/);
215 }
216 
217 DMR *
read_data_plain(const string & file_name,bool debug)218 read_data_plain(const string &file_name, bool debug)
219 {
220     D4BaseTypeFactory *factory = new D4BaseTypeFactory;
221     DMR *dmr = new DMR(factory, "Test_data");
222 
223     fstream in(file_name.c_str(), ios::in|ios::binary);
224 
225     // Gobble up the response's initial set of MIME headers. Normally
226     // a client would extract information from these headers.
227     remove_mime_header(in);
228 
229     chunked_istream cis(in, CHUNK_SIZE);
230 
231     // parse the DMR, stopping when the boundary is found.
232     try {
233         // force chunk read
234         // get chunk size
235         int chunk_size = cis.read_next_chunk();
236         // get chunk
237         char chunk[chunk_size];
238         cis.read(chunk, chunk_size);
239         // parse char * with given size
240     	D4ParserSax2 parser;
241 
242     	// Mirror the behavior in D4Connect where we are permissive with DAP4
243     	// data responses' parsing, as per Hyrax-98 in Jira. jhrg 4/13/16
244     	parser.set_strict(false);
245 
246     	// '-2' to discard the CRLF pair
247         parser.intern(chunk, chunk_size-2, dmr, debug);
248     }
249     catch(Error &e) {
250     	delete factory;
251     	delete dmr;
252     	cerr << "Exception: " << e.get_error_message() << endl;
253     	return 0;
254     }
255     catch(std::exception &e) {
256     	delete factory;
257     	delete dmr;
258     	cerr << "Exception: " << e.what() << endl;
259     	return 0;
260     }
261     catch(...) {
262     	delete factory;
263     	delete dmr;
264     	cerr << "Exception: unknown error" << endl;
265     	return 0;
266     }
267 
268     D4StreamUnMarshaller um(cis, cis.twiddle_bytes());
269 
270     dmr->root()->deserialize(um, *dmr);
271 
272     delete factory;
273     dmr->set_factory(0);
274     return dmr;
275 }
276 
usage()277 static void usage()
278 {
279     cerr << "Usage: dmr-test -p|s|t|i <file> [-c <expr>] [-f <function expression>] [-d -x -e]" << endl
280             << "p: Parse a file (use \"-\" for stdin; if a ce or a function is passed those are parsed too)" << endl
281             << "s: Send: parse and then 'send' a response to a file" << endl
282             << "t: Transmit: parse, send and then read the response file" << endl
283             << "i: Intern values (ce and function will be ignored by this)" << endl
284             << "c: Constraint expression " << endl
285             << "f: Function expression" << endl
286             << "d: turn on detailed xml parser debugging" << endl
287             << "D: turn on detailed ce parser debugging" << endl
288             << "x: print the binary object(s) built by the parse, send, trans or intern operations." << endl
289             << "e: use sEries values." << endl;
290 }
291 
292 int
main(int argc,char * argv[])293 main(int argc, char *argv[])
294 {
295     GetOpt getopt(argc, argv, "p:s:t:i:c:f:xdDeh?");
296     int option_char;
297     bool parse = false;
298     bool debug = false;
299     bool print = false;
300     bool send = false;
301     bool trans = false;
302     bool intern = false;
303     bool series_values = false;
304     bool constrained = false;
305     bool functional = false;
306     bool ce_parser_debug = false;
307     string name = "";
308     string ce = "";
309     string function = "";
310 
311     // process options
312 
313     while ((option_char = getopt()) != -1)
314         switch (option_char) {
315         case 'p':
316             parse = true;
317             name = getopt.optarg;
318             break;
319 
320         case 's':
321         	send = true;
322         	name = getopt.optarg;
323         	break;
324 
325         case 't':
326         	trans = true;
327         	name = getopt.optarg;
328         	break;
329 
330         case 'i':
331         	intern = true;
332         	name = getopt.optarg;
333         	break;
334 
335         case 'c':
336         	constrained = true;
337         	ce = getopt.optarg;
338         	break;
339 
340         case 'f':
341         	functional = true;
342         	function = getopt.optarg;
343         	break;
344 
345         case 'd':
346             debug = true;
347             break;
348 
349         case 'D':
350             ce_parser_debug = true;
351             break;
352 
353         case 'x':
354             print = true;
355             break;
356 
357         case 'e':
358         	series_values = true;
359         	break;
360 
361         case '?':
362         case 'h':
363             usage();
364             return 0;
365 
366         default:
367             cerr << "Error: ";
368             usage();
369             return 1;
370         }
371 
372     if (! (parse || send || trans || intern)) {
373         cerr << "Error: ";
374         usage();
375         return 1;
376     }
377 
378     try {
379 		if (parse) {
380 			DMR *dmr = test_dap4_parser(name, debug, print);
381 
382 			// The CE Parser
383 			if (!ce.empty()) {
384 				try {
385 					D4ConstraintEvaluator parser(dmr);
386 					if (ce_parser_debug) parser.set_trace_parsing(true);
387 					bool parse_ok = parser.parse(ce);
388 					if (!parse_ok)
389 						cout << "CE Parse Failed" << endl;
390 					else
391 						cout << "CE Parse OK" << endl;
392 				}
393 				catch (Error &e) {
394 					cerr << "CE Parse error: " << e.get_error_message() << endl;
395 				}
396 				catch (...) {
397 					cerr << "Ce Parse error: Unknown exception thrown by parser" << endl;
398 				}
399 			}
400 
401 			// The Function Parser
402 			if (!function.empty()) {
403 				try {
404 					ServerFunctionsList *sf_list = ServerFunctionsList::TheList();
405 				    ServerFunction *scale = new D4TestFunction;
406 				    sf_list->add_function(scale);
407 
408 					D4FunctionEvaluator parser(dmr, sf_list);
409 					if (ce_parser_debug) parser.set_trace_parsing(true);
410 					bool parse_ok = parser.parse(function);
411 					if (!parse_ok)
412 						cout << "Function Parse Failed" << endl;
413 					else
414 						cout << "Function Parse OK" << endl;
415 				}
416 				catch (Error &e) {
417 					cerr << "Function Parse error: " << e.get_error_message() << endl;
418 				}
419 				catch (...) {
420 					cerr << "Function Parse error: Unknown exception thrown by parser" << endl;
421 				}
422 			}
423 
424 			delete dmr;
425 		}
426 
427 		if (send) {
428         	DMR *dmr = test_dap4_parser(name, debug, print);
429 
430         	string file_name = send_data(dmr, ce, function, series_values, ce_parser_debug);
431         	if (print)
432         		cout << "Response file: " << file_name << endl;
433         	delete dmr;
434         }
435 
436         if (trans) {
437         	DMR *dmr = test_dap4_parser(name, debug, print);
438         	string file_name = send_data(dmr, ce, function, series_values, ce_parser_debug);
439          	delete dmr;
440 
441         	DMR *client = read_data_plain(file_name, debug);
442 
443         	if (print) {
444         		XMLWriter xml;
445         		// received data never have send_p set; don't set 'constrained'
446         		client->print_dap4(xml, false /* constrained */);
447         		cout << xml.get_doc() << endl;
448 
449 				cout << "The data:" << endl;
450         	}
451 
452         	// if trans is used, the data are printed regardless of print's value
453     		client->root()->print_val(cout, "", false);
454     		cout << endl;
455 
456         	delete client;
457         }
458 
459         if (intern) {
460         	DMR *dmr = test_dap4_parser(name, debug, print);
461         	intern_data(dmr, /*ce,*/ series_values);
462 
463         	if (print) {
464         		XMLWriter xml;
465         		dmr->print_dap4(xml, false /*constrained*/);
466         		cout << xml.get_doc() << endl;
467 
468 				cout << "The data:" << endl;
469         	}
470 
471         	// if trans is used, the data are printed regardless of print's value
472     		dmr->root()->print_val(cout, /*space*/"", false);
473     		cout << endl;
474 
475         	delete dmr;
476         }
477     }
478     catch (Error &e) {
479         cerr << "Error: " << e.get_error_message() << endl;
480         return 1;
481     }
482 
483     return 0;
484 }
485 
486