bes Updated for version 3.21.1
The Backend Server (BES) is the lower two tiers of the Hyrax data server
FONcTransform.cc
1// FONcTransform.cc
2
3// This file is part of BES Netcdf File Out Module
4
5// Copyright (c) 2004,2005 University Corporation for Atmospheric Research
6// Author: Patrick West <pwest@ucar.edu> and Jose Garcia <jgarcia@ucar.edu>
7//
8// This library is free software; you can redistribute it and/or
9// modify it under the terms of the GNU Lesser General Public
10// License as published by the Free Software Foundation; either
11// version 2.1 of the License, or (at your option) any later version.
12//
13// This library is distributed in the hope that it will be useful,
14// but WITHOUT ANY WARRANTY; without even the implied warranty of
15// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16// Lesser General Public License for more details.
17//
18// You should have received a copy of the GNU Lesser General Public
19// License along with this library; if not, write to the Free Software
20// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21//
22// You can contact University Corporation for Atmospheric Research at
23// 3080 Center Green Drive, Boulder, CO 80301
24
25// (c) COPYRIGHT University Corporation for Atmospheric Research 2004-2005
26// Please read the full copyright statement in the file COPYRIGHT_UCAR.
27//
28// Authors:
29// pwest Patrick West <pwest@ucar.edu>
30// jgarcia Jose Garcia <jgarcia@ucar.edu>
31// kyang Kent Yang <myang6@hdfgroup.org> (for DAP4/netCDF-4 enhancement)
32// slloyd Samuel Lloyd <slloyd@opendap.org> (netCDF file streaming)
33
34#include "config.h"
35
36#include <sstream>
37
38#include <netcdf.h>
39
40#include <libdap/DDS.h>
41#include <libdap/DMR.h>
42#include <libdap/D4Group.h>
43#include <libdap/D4Attributes.h>
44#include <libdap/Structure.h>
45#include <libdap/Array.h>
46#include <libdap/Grid.h>
47#include <libdap/Sequence.h>
48
49#include <BESResponseObject.h>
50#include <BESDapResponseBuilder.h>
51#include <BESDataHandlerInterface.h>
52#include <BESUtil.h>
53#include <TempFile.h>
54#include <BESDapNames.h>
55#include <BESDataNames.h>
56#include <BESDataDDSResponse.h>
57#include <BESDMRResponse.h>
58#include <BESRequestHandlerList.h>
59#include <BESDapFunctionResponseCache.h>
60#include <BESDebug.h>
61#include <BESInternalError.h>
62#include <BESInternalFatalError.h>
63#include "BESSyntaxUserError.h"
64#include "RequestServiceTimer.h"
65
66#include "DapFunctionUtils.h"
67#include "DapUtils.h"
68
69#include "FONcRequestHandler.h" // for the keys
70
71#include "FONcTransform.h"
72#include "FONcUtils.h"
73#include "FONcBaseType.h"
74#include "FONcAttributes.h"
75#include "FONcTransmitter.h"
76#include "history_utils.h"
77#include "FONcNames.h"
78
79using namespace libdap;
80using namespace std;
81
82#define MODULE "fonc"
83#define prolog std::string("FONcTransform::").append(__func__).append("() - ")
84
85#define FOUR_GB_IN_KB (4294967296/1024)
86#define TWO_GB_IN_KB (2147483648/1024)
87#define MSG_LABEL_CLASSIC_MODEL " (classic model)"
88#define MSG_LABEL_SIXTYFOUR_BIT_MODEL " (64-bit offset model)"
89
101 const string &ncVersion)
102 : d_obj(obj), d_dhi(dhi), _localfile(localfile), _returnAs(ncVersion) {
103 if (!d_obj) {
104 throw BESInternalError("File out netcdf, null BESResponseObject passed to constructor", __FILE__, __LINE__);
105 }
106 if (_localfile.empty()) {
107 throw BESInternalError("File out netcdf, empty local file name passed to constructor", __FILE__, __LINE__);
108 }
109
110 // if there is a variable, attribute, dimension name that is not
111 // compliant with netcdf naming conventions then we will create
112 // a new name. If the new name does not begin with an alpha
113 // character then we will prefix it with name_prefix. We will
114 // get this prefix from the type of data that we are reading in,
115 // such as nc, h4, h5, ff, jg, etc...
116 dhi->first_container();
117 if (dhi->container) {
118 FONcUtils::name_prefix = dhi->container->get_container_type() + "_";
119 }
120 else {
121 FONcUtils::name_prefix = "nc_";
122 }
123}
124
130 for (auto &b: _fonc_vars) {
131 delete b;
132 }
133 for (auto &b: _total_fonc_vars_in_grp) {
134 delete b;
135 }
136 // _dmr is not managed by the BESDMRResponse class in this code. However,
137 // _dds still is. jhrg 8/13/21
138 delete _dmr;
139}
140
150string FONcTransform::too_big_error_msg(
151 const unsigned dap_version,
152 const string &return_encoding,
153 const unsigned long long dap2_response_size_kb,
154 const unsigned long long contextual_max_response_size_kb,
155 const string &ce
156){
157
158 stringstream msg;
159
160 msg << "Your request was for a (DAP"<< dap_version << " data model response) to be encoded as ";
161 msg << return_encoding << ". ";
162 msg << "The response to your specific request will produce a " << dap2_response_size_kb;
163 msg << " kilobyte response. On this server the response size for your request is limited to ";
164 msg << contextual_max_response_size_kb << " kilobytes. ";
165
166 msg << "The server is configured to allow ";
167 auto conf_max_request_size_kb =FONcRequestHandler::get_request_max_size_kb();
168 if(conf_max_request_size_kb==0){
169 msg << " responses of unlimited size. ";
170 }
171 else {
172 msg << "responses as large as: " << conf_max_request_size_kb <<" kilobytes. ";
173 }
174
175 if (FONcTransform::_returnAs == FONC_RETURN_AS_NETCDF3) {
176 msg << "Additionally, the requested response encoding " << return_encoding << " is structurally limited to ";
177 if (FONcRequestHandler::nc3_classic_format) {
178 msg << TWO_GB_IN_KB << " kilobytes" << MSG_LABEL_CLASSIC_MODEL << ".";
179 }
180 else {
181 msg << FOUR_GB_IN_KB << " kilobytes" << MSG_LABEL_SIXTYFOUR_BIT_MODEL << ".";
182 }
183 msg << "One thing to try would be to reissue the the request, but change the requested response encoding ";
184 msg << "to NetCDF-4. This can be accomplished with the buttons in the Data Request Form, or by modifying ";
185 msg << "the request URL by changing the terminal path suffix from \".nc\" to \".nc4\". ";
186 }
187
188 if(ce.empty()){
189 msg << "I've noticed that no constraint expression accompanied your request. ";
190 } else {
191 msg << "Your request employed the constraint expression: \"" << ce << "\" ";
192 }
193 msg << "You may also reduce the size of the request by choosing just the variable(s) you need and/or by ";
194 msg << "using the DAP index based array sub-setting syntax to additionally limit the amount of data requested.";
195 return msg.str();
196}
197
198
208void FONcTransform::set_max_size_and_encoding(unsigned long long &max_request_size_kb, string &return_encoding){
209
210 return_encoding.clear();
211
212 // The following conditional accomplishes two things:
213 // 1) It correctly controls the values of "max_request_size_kb" so that even if it's
214 // set to unlimited (aka 0) rational limits will be enforced based on the type of
215 // response coding that was requested.
216 // 2) It constructs the string "return_encoding" for use in debugging and as
217 // a component of the "it's too big" error message.
218 if (FONcTransform::_returnAs == FONC_RETURN_AS_NETCDF3) {
219 return_encoding = string(FONC_RETURN_AS_NETCDF3).append("-3 ");
220 if (FONcRequestHandler::nc3_classic_format) {
221 return_encoding += MSG_LABEL_CLASSIC_MODEL;
222 if (max_request_size_kb == 0 || max_request_size_kb >= TWO_GB_IN_KB) {
223 max_request_size_kb = TWO_GB_IN_KB - 1 /* kb */;
224 BESDEBUG(MODULE, prolog << "Configured max request size was incompatible with NetCDF-3 classic format. " <<
225 "Reset to: " << max_request_size_kb << endl);
226 }
227 }
228 else {
229 return_encoding += MSG_LABEL_SIXTYFOUR_BIT_MODEL;
230 if (max_request_size_kb == 0 || max_request_size_kb >= FOUR_GB_IN_KB) {
231 max_request_size_kb = FOUR_GB_IN_KB - 1 /* kb */;
232 BESDEBUG(MODULE, prolog << "Configured max request size was incompatible with NetCDF-3 w/64-bit offset format. " <<
233 "Reset to: " << max_request_size_kb << endl);
234 }
235 }
236 }
237 else {
238 return_encoding = FONC_RETURN_AS_NETCDF4;
239 if (FONcRequestHandler::nc3_classic_format) {
240 return_encoding += MSG_LABEL_CLASSIC_MODEL;
241 }
242 }
243 BESDEBUG(MODULE, prolog << "return_encoding: " << return_encoding << endl);
244 BESDEBUG(MODULE, prolog << "max_request_size_kb: " << max_request_size_kb << endl);
245}
246
247
254void FONcTransform::throw_if_dap2_response_too_big(DDS *dds, const string &dap2_ce)
255{
256 string return_encoding;
257
258 unsigned long long max_response_size_kb = FONcRequestHandler::get_request_max_size_kb();
259 BESDEBUG(MODULE, prolog << "Configured max_request_size_kb: " << max_response_size_kb << endl);
260
261 unsigned long long dap2_response_size_kb = dds->get_request_size_kb(true);
262 BESDEBUG(MODULE, prolog << "dds->get_request_size_kb(): " << dap2_response_size_kb << endl);
263
264 set_max_size_and_encoding(max_response_size_kb, return_encoding);
265
266 // set the max request size in kilobytes for testing if the request is too large
267 dds->set_response_limit_kb(max_response_size_kb);
268
269 if (dds->too_big()) {
270 string err_msg = too_big_error_msg(2,return_encoding,dap2_response_size_kb, max_response_size_kb, dap2_ce);
271 throw BESSyntaxUserError(err_msg,__FILE__,__LINE__);
272 }
273}
274
284
285 BESDEBUG(MODULE, prolog << "BEGIN" << endl);
286 BESDEBUG(MODULE, prolog << "Reading data into DataDDS" << endl);
287
289
290 d_dhi->first_container();
291
292 auto bdds = dynamic_cast<BESDataDDSResponse *>(d_obj);
293 if (!bdds) throw BESInternalFatalError("Expected a BESDataDDSResponse instance", __FILE__, __LINE__);
294
295 _dds = bdds->get_dds();
296
298
299 besDRB.set_dataset_name(_dds->filename());
300 besDRB.set_ce(d_dhi->data[POST_CONSTRAINT]);
301 besDRB.set_async_accepted(d_dhi->data[ASYNC]);
302 besDRB.set_store_result(d_dhi->data[STORE_RESULT]);
303
304
305 // This function is used by all fileout modules, and they need to include the attributes in data access.
306 // So obtain the attributes if necessary. KY 2019-10-30
307 if (bdds->get_ia_flag() == false) {
308 BESRequestHandler *besRH = BESRequestHandlerList::TheList()->find_handler(
309 d_dhi->container->get_container_type());
310 besRH->add_attributes(*d_dhi);
311 }
312
313 ConstraintEvaluator &eval = bdds->get_ce();
314
315 // Split constraint into two halves; stores the function and non-function parts in this instance.
316 besDRB.split_ce(eval);
317 // If there are functions, parse them and eval.
318 // Use that DDS and parse the non-function ce
319 // Serialize using the second ce and the second dds
320 if (!besDRB.get_btp_func_ce().empty()) {
321 BESDEBUG(MODULE, prolog << "Found function(s) in CE: " << besDRB.get_btp_func_ce() << endl);
322
323 BESDapFunctionResponseCache *responseCache = BESDapFunctionResponseCache::get_instance();
324
325 ConstraintEvaluator func_eval;
326 DDS *fdds = nullptr;
327 if (responseCache && responseCache->can_be_cached(_dds, besDRB.get_btp_func_ce())) {
328 fdds = responseCache->get_or_cache_dataset(_dds, besDRB.get_btp_func_ce());
329 }
330 else {
331 func_eval.parse_constraint(besDRB.get_btp_func_ce(), *_dds);
332 fdds = func_eval.eval_function_clauses(*_dds);
333 }
334
335 delete _dds; // Delete so that we can ...
336 bdds->set_dds(fdds); // Transfer management responsibility
337 _dds = fdds;
338
339 // Server functions might mark (i.e. setting send_p) so variables will use their read()
340 // methods. Clear that so the CE in d_dap2ce will control what is
341 // sent. If that is empty (there was only a function call), all
342 // variables in the intermediate DDS (i.e., the function
343 // result) will be sent.
344 _dds->mark_all(false);
345
346 // Look for one or more top level Structures whose name indicates (by way of ending with
347 // "_uwrap") that their contents should be moved to the top level.
348 //
349 // This is in support of a hack around the current API where server side functions
350 // may only return a single DAP object and not a collection of objects. The name suffix
351 // "_unwrap" is used as a signal from the function to the the various response
352 // builders and transmitters that the representation needs to be altered before
353 // transmission, and that in fact is what happens in our friend
354 // promote_function_output_structures()
355 promote_function_output_structures(_dds);
356 }
357
358
359 // evaluate the rest of the CE - the part that follows the function calls.
360 eval.parse_constraint(besDRB.get_ce(), *_dds);
361
362 _dds->tag_nested_sequences(); // Tag Sequences as Parent or Leaf node.
363
364#if 0 //removed due not being needed
365 vector<BaseType *> projected_dap4_variable_inventory;
366 bool d4_true = d4_tools::is_dap4_projected(_dds, projected_dap4_variable_inventory);
367
374
375 if (d4_true){
376 throw BESSyntaxUserError(
377 "request cannot be fulfilled because the response contains types that are not compatible with the requested encoding",
378 __FILE__,
379 __LINE__);
380 }
381#endif
382
383 throw_if_dap2_response_too_big(_dds, besDRB.get_ce());
384 dap_utils::throw_for_dap4_typed_vars_or_attrs(_dds,__FILE__,__LINE__);
385
386 // Convert the DDS into an internal format to keep track of
387 // variables, arrays, shared dimensions, grids, common maps,
388 // embedded structures. It only grabs the variables that are to be
389 // sent.
390 for (auto vi = _dds->var_begin(), ve = _dds->var_end(); vi != ve; vi++) {
391 if ((*vi)->send_p()) {
392 BESDEBUG(MODULE, prolog << "Converting variable '" << (*vi)->name() << "'" << endl);
393
394 // This is a factory class call, and 'fg' is specialized for '*vi'
395 FONcBaseType *fb = FONcUtils::convert((*vi), FONcTransform::_returnAs, FONcRequestHandler::classic_model);
396
397 _fonc_vars.push_back(fb);
398 vector <string> embed;
399 fb->convert(embed);
400 }
401 }
402
403 fonc_history_util::updateHistoryAttributes(_dds, d_dhi->data[POST_CONSTRAINT]);
404
405 // Open the file for writing
406 int stax;
407 if (FONcTransform::_returnAs == FONC_RETURN_AS_NETCDF4) {
408 if (FONcRequestHandler::classic_model) {
409 BESDEBUG(MODULE, prolog << "Opening NetCDF-4 cache file in classic mode. fileName: "
410 << _localfile << endl);
411 stax = nc_create(_localfile.c_str(), NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL, &_ncid);
412 }
413 else {
414 BESDEBUG(MODULE, prolog << "Opening NetCDF-4 cache file. fileName: " << _localfile << endl);
415 stax = nc_create(_localfile.c_str(), NC_CLOBBER | NC_NETCDF4, &_ncid);
416 }
417 }
418 else {
419 BESDEBUG(MODULE, prolog << "Opening NetCDF-3 cache file. fileName: " << _localfile << endl);
420 if (FONcRequestHandler::nc3_classic_format)
421 stax = nc_create(_localfile.c_str(), NC_CLOBBER, &_ncid);
422 else
423 stax = nc_create(_localfile.c_str(), NC_CLOBBER | NC_64BIT_OFFSET, &_ncid);
424 }
425
426 if (stax != NC_NOERR) {
427 FONcUtils::handle_error(stax, prolog + "Call to nc_create() failed for file: " + _localfile, __FILE__, __LINE__);
428 }
429
430 int current_fill_prop_vaule;
431
432 stax = nc_set_fill(_ncid, NC_NOFILL, &current_fill_prop_vaule);
433 if (stax != NC_NOERR) {
434 FONcUtils::handle_error(stax, "File out netcdf, unable to set fill to NC_NOFILL: " + _localfile, __FILE__,
435 __LINE__);
436 }
437
438 try {
439 // Here we will be defining the variables of the netcdf and
440 // adding attributes. To do this we must be in define mode.
441 nc_redef(_ncid);
442
443 // For each converted FONc object, call define on it to define
444 // that object to the netcdf file. This also adds the attributes
445 // for the variables to the netcdf file
446 for (FONcBaseType *fbt: _fonc_vars) {
447 BESDEBUG(MODULE, prolog << "Defining variable: " << fbt->name() << endl);
448 fbt->define(_ncid);
449 }
450
451 if (FONcRequestHandler::no_global_attrs == false) {
452 // Add any global attributes to the netcdf file
453 AttrTable &globals = _dds->get_attr_table();
454 BESDEBUG(MODULE, prolog << "Adding Global Attributes" << endl << globals << endl);
455 bool is_netCDF_enhanced = false;
456 if (FONcTransform::_returnAs == FONC_RETURN_AS_NETCDF4 && FONcRequestHandler::classic_model == false)
457 is_netCDF_enhanced = true;
458 FONcAttributes::add_attributes(_ncid, NC_GLOBAL, globals, "", "", is_netCDF_enhanced);
459 // We could add the json history directly to the netcdf file here. For now,
460 // this code, which adds it to the global attribute table and then moves
461 // those into the netcdf file, is working. There are two other places in the
462 // file where this is true. Search for '***' jhrg 2/28/22
463 }
464
465 // We are done defining the variables, dimensions, and
466 // attributes of the netcdf file. End the define mode.
467 int stax = nc_enddef(_ncid);
468
469 // Check error for nc_enddef. Handling of HDF failures
470 // can be detected here rather than later. KY 2012-10-25
471 if (stax != NC_NOERR) {
472 FONcUtils::handle_error(stax, "File out netcdf, unable to end the define mode: " + _localfile, __FILE__,
473 __LINE__);
474 }
475 for (FONcBaseType *fbt: _fonc_vars) {
476 BESDEBUG(MODULE, prolog << "Writing data for variable: " << fbt->name() << endl);
477
478 fbt->set_dds(_dds);
479 fbt->set_eval(&eval);
480
481 fbt->write(_ncid);
482 nc_sync(_ncid);
483 }
484
485 stax = nc_close(_ncid);
486 if (stax != NC_NOERR)
487 FONcUtils::handle_error(stax, "File out netcdf, unable to close: " + _localfile, __FILE__, __LINE__);
488
489 }
490 catch (const BESError &e) {
491 (void) nc_close(_ncid); // ignore the error at this point
492 throw;
493 }
494}
495
503void FONcTransform::throw_if_dap4_response_too_big(DMR *dmr, const string &dap4_ce)
504{
505 unsigned long long max_response_size_kb = FONcRequestHandler::get_request_max_size_kb();
506 BESDEBUG(MODULE, prolog << "Configured max_request_size_kb: " << max_response_size_kb << endl);
507
508 unsigned long long req_size_kb = dmr->request_size_kb(true);
509 BESDEBUG(MODULE, prolog << "dmr->get_request_size_kb(): " << req_size_kb << endl);
510
511 string return_encoding;
512 set_max_size_and_encoding(max_response_size_kb, return_encoding);
513
514 // set the max request size in kilobytes for testing if the request is too large
515 dmr->set_response_limit_kb(max_response_size_kb);
516
517 if (dmr->too_big()) {
518 string err_msg = too_big_error_msg(4,return_encoding,req_size_kb, max_response_size_kb, dap4_ce);
519 throw BESSyntaxUserError(err_msg,__FILE__,__LINE__);
520 }
521}
522
532 BESDEBUG(MODULE, prolog << "BEGIN" << endl);
533
535
536 d_dhi->first_container();
537
538 BESDapResponseBuilder responseBuilder;
539 _dmr = responseBuilder.setup_dap4_intern_data(d_obj, *d_dhi).release();
540
541 _dmr->set_response_limit_kb(FONcRequestHandler::get_request_max_size_kb());
542
543 vector<string> inventory;
544 bool d4_true = _dmr->is_dap4_projected(inventory);
545
546 if (d4_true && _returnAs == "netcdf"){
547 stringstream msg;
548 msg << "This dataset contains variables and/or attributes whose data types are not compatible with the " << endl;
549 msg << "NetCDF-3 data model. If your request includes any of variables represented by one of these " << endl;
550 msg << "incompatible variables and/or attributes and you choose the “NetCDF-3” download encoding, " << endl;
551 msg << "your request will FAIL. " << endl;
552 msg << endl;
553 msg << "You may also try constraining your request to omit the problematic data type(s), " << endl;
554 msg << "or ask for a different encoding such as DAP4 binary or NetCDF-4." << endl;
555 msg << "There are" << inventory.size() << " incompatible variables referenced in your request." << endl;
556 msg << "Incompatible variables: " << endl;
557 msg << endl;
558 for(const auto &entry: inventory){
559 msg << " " << entry << endl;
560 }
561 throw BESSyntaxUserError(
562 msg.str(),
563 __FILE__,
564 __LINE__);
565 }
566
567 throw_if_dap4_response_too_big(_dmr,responseBuilder.get_dap4ce() );
568
570
571 besDRB.set_dataset_name(_dmr->filename());
572
573 // Added set of DAP4 fields. jhrg 5/30/21
574 besDRB.set_dap4ce(d_dhi->data[DAP4_CONSTRAINT]);
575 besDRB.set_dap4function(d_dhi->data[DAP4_FUNCTION]);
576
577 besDRB.set_async_accepted(d_dhi->data[ASYNC]);
578 besDRB.set_store_result(d_dhi->data[STORE_RESULT]);
579
580 // Here we need to check if we need to reduce the redundant dimension names.
581 if (FONcRequestHandler::reduce_dim == true) {
582 do_reduce_dim = check_reduce_dim();
583 if (do_reduce_dim)
584 build_reduce_dim();
585#if !NDEBUG
586 if (do_reduce_dim)
587 BESDEBUG(MODULE, prolog << "reduced dimensions" << endl);
588 else
589 BESDEBUG(MODULE, prolog << "Not reduced dimensions" << endl);
590
591 if (do_reduce_dim) {
592 D4Group *root_grp_debug = _dmr->root();
593 for (auto &var:root_grp_debug->variables()) {
594
595 if (var->type() == dods_array_c) {
596 auto t_a = dynamic_cast<Array *>(var);
597 Array::Dim_iter dim_i = t_a->dim_begin();
598 Array::Dim_iter dim_e = t_a->dim_end();
599 for (; dim_i != dim_e; dim_i++) {
600 BESDEBUG(MODULE, prolog << "CHANGED dim name: " << dim_i->name<<endl);
601 }
602 }
603 }
604
605 D4Dimensions *root_dims = root_grp_debug->dims();
606 for (D4Dimensions::D4DimensionsIter di = root_dims->dim_begin(), de = root_dims->dim_end(); di != de; ++di) {
607 BESDEBUG(MODULE, prolog << "transform_dap4() - check dimensions" << endl);
608 BESDEBUG(MODULE, prolog << "transform_dap4() - dim name is: " << (*di)->name() << endl);
609 BESDEBUG(MODULE, prolog << "transform_dap4() - dim size is: " << (*di)->size() << endl);
610 BESDEBUG(MODULE, prolog << "transform_dap4() - fully_qualfied_dim name is: " << (*di)->fully_qualified_name() << endl);
611 }
612 }
613
614#endif
615 }
616 // Check if direct_io_flag is set for any Array variables. If the global dio flag is false, we don't need to loop through
617 // every variable to check if the direct IO can be applied.
618 if (FONC_RETURN_AS_NETCDF4 == FONcTransform::_returnAs && false == FONcRequestHandler::classic_model) {
619 global_dio_flag = _dmr->get_global_dio_flag();
620
621#if !NDEBUG
622 if(global_dio_flag) {
623 BESDEBUG(MODULE, prolog << "global_dio_flag is true" << endl);
624 }
625 else
626 BESDEBUG(MODULE, prolog << "global_dio_flag is false" << endl);
627#endif
628
629 }
630
631 // Convert the DMR into an internal format to keep track of
632 // variables, arrays, shared dimensions, grids, common maps,
633 // embedded structures. It only grabs the variables that are to be
634 // sent.
635
636
637 // First check if this DMR has groups etc.
638 bool support_group = check_group_support();
639
640 if (true == support_group) {
641
642 int stax = -1;
643 BESDEBUG(MODULE, prolog << "Opening NetCDF-4 cache file. fileName: " << _localfile << endl);
644 stax = nc_create(_localfile.c_str(), NC_CLOBBER | NC_NETCDF4, &_ncid);
645 if (stax != NC_NOERR)
646 FONcUtils::handle_error(stax, prolog + "Call to nc_create() failed for file: " + _localfile, __FILE__, __LINE__);
647
648 D4Group *root_grp = _dmr->root();
649
650 // Declare the dimname to dimid map to handle netCDF-4 dimensions
651 map<string, int> fdimname_to_id;
652
653 // Generate a list of the groups in the final netCDF file.
654 // The attributes of these groups should be included.
655 gen_included_grp_list(root_grp);
656
657#if !NDEBUG
658 for (std::set<string>::iterator it = _included_grp_names.begin(); it != _included_grp_names.end(); ++it)
659 BESDEBUG(MODULE, prolog << "Included group list name is: " << *it << endl);
660#endif
661 // Build a global dimension name table for all variables if
662 // the constraint is not empty!
663 check_and_obtain_dimensions(root_grp, true);
664
665 // Don't remove the following code, they are for debugging.
666#if !NDEBUG
668
669 for (it = GFQN_dimname_to_dimsize.begin(); it != GFQN_dimname_to_dimsize.end(); ++it) {
670 BESDEBUG(MODULE, prolog << "Final GFQN dim name is: " << it->first << endl);
671 BESDEBUG(MODULE, prolog << "Final GFQN dim size is: " << it->second << endl);
672 }
673
674 for (it = VFQN_dimname_to_dimsize.begin(); it != VFQN_dimname_to_dimsize.end(); ++it) {
675 BESDEBUG(MODULE, prolog << "Final VFQN dim name is: " << it->first << endl);
676 BESDEBUG(MODULE, prolog << "Final VFQN dim size is: " << it->second << endl);
677 }
678#endif
679
680 // DAP4 requires the DAP4 dimension sizes defined in the group should be changed
681 // according to the corresponding variable sizes. Check section 8.6.2 at
682 // https://docs.opendap.org/index.php/DAP4:_Specification_Volume_1
683 //
685 for (git = GFQN_dimname_to_dimsize.begin(); git != GFQN_dimname_to_dimsize.end(); ++git) {
686 for (vit = VFQN_dimname_to_dimsize.begin(); vit != VFQN_dimname_to_dimsize.end(); ++vit) {
687 if (git->first == vit->first) {
688 if (git->second != vit->second)
689 git->second = vit->second;
690 break;
691 }
692 }
693 }
694
695 // This part of code is to address the possible dimension name conflict
696 // when variables in the constraint don't have dimension names. Fileout netCDF
697 // adds the fake dimensions such as dim1, dim2...to these variables.
698 // If these dimension names are used by
699 // the file to be handled, the dimension conflict will corrupt the final output.
700 // The idea is to find if there are any dimension names like dim1, dim2 ...
701 // under the root group.
702 // We will remember them and not use these names as fake dimension names.
703 //
704 // Obtain the dim. names under the root group
705 vector <string> root_d4_dimname_list;
706 for (git = GFQN_dimname_to_dimsize.begin(); git != GFQN_dimname_to_dimsize.end(); ++git) {
707 string d4_temp_dimname = git->first.substr(1);
708 //BESDEBUG(MODULE, prolog << "d4_temp_dimname: "<<d4_temp_dimname<<endl);
709 if (d4_temp_dimname.find('/') == string::npos)
710 root_d4_dimname_list.push_back(d4_temp_dimname);
711 }
712
713#if !NDEBUG
714 for (unsigned int i = 0; i < root_d4_dimname_list.size(); i++)
715 BESDEBUG(MODULE, prolog << "root_d4 dim name is: " << root_d4_dimname_list[i] << endl);
716#endif
717
718 // Only remember the root dimension names that are like "dim1,dim2,..."
719 vector<int> root_dim_suffix_nums;
720 for (unsigned int i = 0; i < root_d4_dimname_list.size(); i++) {
721 if (root_d4_dimname_list[i].size() < 4)
722 continue;
723 else if (root_d4_dimname_list[i].substr(0, 3) != "dim")
724 continue;
725 else {
726 string temp_suffix = root_d4_dimname_list[i].substr(3);
727 //BESDEBUG(MODULE, prolog << "temp_suffix: "<<temp_suffix<<endl);
728 bool ignored_suffix = false;
729 for (unsigned int j = 0; j < temp_suffix.size(); j++) {
730 if (!isdigit(temp_suffix[j])) {
731 ignored_suffix = true;
732 break;
733 }
734 }
735 if (ignored_suffix == true)
736 continue;
737 else
738 root_dim_suffix_nums.push_back(atoi(temp_suffix.c_str()));
739 }
740 }
741
742#if !NDEBUG
743 for (unsigned int i = 0; i < root_dim_suffix_nums.size(); i++)
744 BESDEBUG(MODULE, prolog << "root_dim_suffix_nums: " << root_dim_suffix_nums[i] << endl);
745
746
747 for (it = GFQN_dimname_to_dimsize.begin(); it != GFQN_dimname_to_dimsize.end(); ++it) {
748 BESDEBUG(MODULE, prolog << "RFinal GFQN dim name is: " << it->first << endl);
749 BESDEBUG(MODULE, prolog << "RFinal GFQN dim size is: " << it->second << endl);
750 }
751
752 for (it = VFQN_dimname_to_dimsize.begin(); it != VFQN_dimname_to_dimsize.end(); ++it) {
753 BESDEBUG(MODULE, prolog << "RFinal VFQN dim name is: " << it->first << endl);
754 BESDEBUG(MODULE, prolog << "RFinal VFQN dim size is: " << it->second << endl);
755 }
756#endif
757
758 // Now we transform all the objects(including groups) to netCDF-4
759 transform_dap4_group(root_grp, true, _ncid, fdimname_to_id, root_dim_suffix_nums);
760 stax = nc_close(_ncid);
761 if (stax != NC_NOERR)
762 FONcUtils::handle_error(stax, "File out netcdf, unable to close: " + _localfile, __FILE__, __LINE__);
763
764 }
765 else // No group, handle as the classic way
766 transform_dap4_no_group();
767
768 BESDEBUG(MODULE, prolog << "END" << endl);
769
770 return;
771}
772
778void FONcTransform::transform_dap4_no_group() {
779
780 D4Group *root_grp = _dmr->root();
781#if !NDEBUG
782 D4Dimensions *root_dims = root_grp->dims();
783 for (D4Dimensions::D4DimensionsIter di = root_dims->dim_begin(), de = root_dims->dim_end(); di != de; ++di) {
784 BESDEBUG(MODULE, prolog << "transform_dap4() - check dimensions" << endl);
785 BESDEBUG(MODULE, prolog << "transform_dap4() - dim name is: " << (*di)->name() << endl);
786 BESDEBUG(MODULE, prolog << "transform_dap4() - dim size is: " << (*di)->size() << endl);
787 BESDEBUG(MODULE, prolog << "transform_dap4() - fully_qualfied_dim name is: " << (*di)->fully_qualified_name() << endl);
788 }
789#endif
790 Constructor::Vars_iter vi = root_grp->var_begin();
791 Constructor::Vars_iter ve = root_grp->var_end();
792
793 // If the global_dio_flag is false, we cannot do direct IO for this file at all. No need to check every variable.
794 // This is necessary since direct IO cannot apply to the current existing dmrpp files in the earth data cloud. KY 2023-12-11
795 if (global_dio_flag == false) {
796 for (; vi != ve; vi++) {
797 if ((*vi)->send_p()) {
798 BaseType *v = *vi;
799
800 BESDEBUG(MODULE, prolog << "Converting variable '" << v->name() << "'" << endl);
801
802 // This is a factory class call, and 'fg' is specialized for 'v'
803 FONcBaseType *fb = FONcUtils::convert(v, FONcTransform::_returnAs, FONcRequestHandler::classic_model);
804
805 _fonc_vars.push_back(fb);
806
807 vector <string> embed;
808 // This call sets d_is_dap4 to true, and d_is_dap4_group to false. jhrg 2/14/24
809 fb->convert(embed, true, false);
810 }
811 }
812 }
813 else {
814 for (; vi != ve; vi++) {
815 if ((*vi)->send_p()) {
816 BaseType *v = *vi;
817
818 BESDEBUG(MODULE, prolog << "Direct IO is off, Converting variable '" << v->name() << "'" << endl);
819
820 if (v->type() == dods_array_c) {
821 auto t_a = dynamic_cast<Array *>(v);
822 if (t_a->get_dio_flag())
823 set_constraint_var_dio_flag(t_a);
824
825 }
826 // This is a factory class call, and 'fg' is specialized for 'v'
827 FONcBaseType *fb = FONcUtils::convert(v, FONcTransform::_returnAs, FONcRequestHandler::classic_model);
828
829
830 _fonc_vars.push_back(fb);
831
832 vector <string> embed;
833 // This call sets d_is_dap4 to true, and d_is_dap4_group to false. jhrg 2/14/24
834 fb->convert(embed, true, false);
835 }
836 }
837 }
838
839#if !NDEBUG
840 if (root_grp->grp_begin() == root_grp->grp_end())
841 BESDEBUG(MODULE, prolog << "No group " << endl);
842 else
843 BESDEBUG(MODULE, prolog << "Has group " << endl);
844 for (D4Group::groupsIter gi = root_grp->grp_begin(), ge = root_grp->grp_end(); gi != ge; ++gi)
845 BESDEBUG(MODULE, prolog << "Group name: " << (*gi)->name() << endl);
846#endif
847
848 fonc_history_util::updateHistoryAttributes(_dmr, d_dhi->data[POST_CONSTRAINT]);
849
850 // Open the file for writing
851 int stax = -1;
852 if (FONcTransform::_returnAs == FONC_RETURN_AS_NETCDF4) {
853 if (FONcRequestHandler::classic_model) {
854 BESDEBUG(MODULE, prolog << "Opening NetCDF-4 cache file in classic mode. fileName: "
855 << _localfile << endl);
856 stax = nc_create(_localfile.c_str(), NC_CLOBBER | NC_NETCDF4 | NC_CLASSIC_MODEL, &_ncid);
857 }
858 else {
859 BESDEBUG(MODULE, prolog << "Opening NetCDF-4 cache file. fileName: " << _localfile
860 << endl);
861 stax = nc_create(_localfile.c_str(), NC_CLOBBER | NC_NETCDF4, &_ncid);
862 }
863 }
864 else {
865 BESDEBUG(MODULE, prolog << "Opening NetCDF-3 cache file. fileName: " << _localfile
866 << endl);
867 if (FONcRequestHandler::nc3_classic_format)
868 stax = nc_create(_localfile.c_str(), NC_CLOBBER, &_ncid);
869 else
870 stax = nc_create(_localfile.c_str(), NC_CLOBBER | NC_64BIT_OFFSET, &_ncid);
871 }
872
873 if (stax != NC_NOERR) {
874 FONcUtils::handle_error(stax, prolog + "Call to nc_create() failed for file: " + _localfile, __FILE__, __LINE__);
875 }
876
877 try {
878 // Here we will be defining the variables of the netcdf and
879 // adding attributes. To do this we must be in define mode.
880 nc_redef(_ncid);
881
882 // For each converted FONc object, call define on it to define
883 // that object to the netcdf file. This also adds the attributes
884 // for the variables to the netcdf file
885 vector<FONcBaseType *>::iterator i = _fonc_vars.begin();
886 vector<FONcBaseType *>::iterator e = _fonc_vars.end();
887 for (; i != e; i++) {
888 FONcBaseType *fbt = *i;
889 BESDEBUG(MODULE, prolog << "Defining variable: " << fbt->name() << endl);
890 //fbt->set_is_dap4(true);
891 fbt->define(_ncid);
892 }
893
894 if (FONcRequestHandler::no_global_attrs == false) {
895
896 // Add any global attributes to the netcdf file
897 D4Group *root_grp = _dmr->root();
898 D4Attributes *d4_attrs = root_grp->attributes();
899
900 BESDEBUG(MODULE, prolog << "Handle GLOBAL DAP4 attributes " << d4_attrs << endl);
901#if !NDEBUG
902 for (D4Attributes::D4AttributesIter ii = d4_attrs->attribute_begin(), ee = d4_attrs->attribute_end();
903 ii != ee; ++ii) {
904 string name = (*ii)->name();
905 BESDEBUG(MODULE, prolog << "GLOBAL attribute name is " << name << endl);
906 }
907#endif
908 bool is_netCDF_enhanced = false;
909 if (FONcTransform::_returnAs == FONC_RETURN_AS_NETCDF4 && FONcRequestHandler::classic_model == false)
910 is_netCDF_enhanced = true;
911 FONcAttributes::add_dap4_attributes(_ncid, NC_GLOBAL, d4_attrs, "", "", is_netCDF_enhanced);
912 // *** Add the json history here
913 }
914
915 // We are done defining the variables, dimensions, and
916 // attributes of the netcdf file. End the define mode.
917 int stax = nc_enddef(_ncid);
918
919 // Check error for nc_enddef. Handling of HDF failures
920 // can be detected here rather than later. KY 2012-10-25
921 if (stax != NC_NOERR) {
922 FONcUtils::handle_error(stax, "File out netcdf, unable to end the define mode: " + _localfile, __FILE__,
923 __LINE__);
924 }
925
926 // Write everything out
927 i = _fonc_vars.begin();
928 e = _fonc_vars.end();
929 for (; i != e; i++) {
930 FONcBaseType *fbt = *i;
931 RequestServiceTimer::TheTimer()->throw_if_timeout_expired(prolog + "ERROR: bes-timeout expired before transmitting: " + fbt->name() , __FILE__, __LINE__);
932 BESDEBUG(MODULE, prolog << "Writing data for variable: " << fbt->name() << endl);
933 fbt->write(_ncid);
934 }
935
936 stax = nc_close(_ncid);
937 if (stax != NC_NOERR)
938 FONcUtils::handle_error(stax, "File out netcdf, unable to close: " + _localfile, __FILE__, __LINE__);
939 }
940 catch (BESError &e) {
941 (void) nc_close(_ncid); // ignore the error at this point
942 throw;
943 }
944
945}
946
947// Transform the DMR to a netCDF-4 file when there are DAP4 groups.
948void FONcTransform::transform_dap4_group(D4Group *grp,
949 bool is_root_grp,
950 int par_grp_id, map<string, int> &fdimname_to_id,
951 vector<int> &root_dim_suffix_nums) {
952
953 bool included_grp = false;
954
955 if (_dmr->get_ce_empty()) {
956 BESDEBUG(MODULE, prolog << "In group - group name: " << grp->FQN() << endl);
957 included_grp = true;
958 }
959 // Always include the root and its attributes.
960 else if (is_root_grp == true)
961 included_grp = true;
962 else {
963 // Check if this group is in the group list kept in the file.
964 set<string>::iterator iset;
965 if (_included_grp_names.find(grp->FQN()) != _included_grp_names.end())
966 included_grp = true;
967 }
968
969 // Call the internal routine to transform the DMR that has groups if this group is in the group list..
970 // If this group is not in the group list, we know all its subgroups are also not in the list, just stop and return.
971 if (included_grp == true)
972 transform_dap4_group_internal(grp, is_root_grp, par_grp_id, fdimname_to_id, root_dim_suffix_nums);
973 return;
974}
975
984void FONcTransform::transform_dap4_group_internal(D4Group *grp,
985 bool is_root_grp,
986 int par_grp_id, map<string, int> &fdimname_to_id,
987 vector<int> &rds_nums) {
988
989 BESDEBUG(MODULE, prolog << "BEGIN" << endl);
990 int grp_id = -1;
991 int stax = -1;
992
993 fonc_history_util::updateHistoryAttributes(_dmr, d_dhi->data[POST_CONSTRAINT]);
994
995 if (is_root_grp == true)
996 grp_id = _ncid;
997 else {
998 // Here we need to check if there is any special character inside the group name.
999 // If yes, we will replace that character to _ since nc_def_grp will fail if
1000 // there are special characters inside the group name. KY 2025-02-14
1001 string grp_name = (*grp).name();
1002 string new_grp_name = FONcUtils::id2netcdf(grp_name);
1003 stax = nc_def_grp(par_grp_id, new_grp_name.c_str(), &grp_id);
1004 BESDEBUG(MODULE, prolog << "Group name is " << (*grp).name() << endl);
1005 if (stax != NC_NOERR)
1006 FONcUtils::handle_error(stax, "File out netcdf, unable to define group: " + _localfile, __FILE__, __LINE__);
1007
1008 }
1009
1010 D4Dimensions *grp_dims = grp->dims();
1011 for (D4Dimensions::D4DimensionsIter di = grp_dims->dim_begin(), de = grp_dims->dim_end(); di != de; ++di) {
1012
1013#if !NDEBUG
1014 BESDEBUG(MODULE, prolog << "Check dimensions" << endl);
1015 BESDEBUG(MODULE, prolog << "Dim name is: " << (*di)->name() << endl);
1016 BESDEBUG(MODULE, prolog << "Dim size is: " << (*di)->size() << endl);
1017 BESDEBUG(MODULE, prolog << "Fully_qualfied_dim name is: " << (*di)->fully_qualified_name() << endl);
1018#endif
1019
1020 int64_t dimsize = (*di)->size();
1021
1022 // The dimension size may need to be updated because of the expression constraint.
1023 map<string, int64_t>::iterator it;
1024 for (it = GFQN_dimname_to_dimsize.begin(); it != GFQN_dimname_to_dimsize.end(); ++it) {
1025 if (it->first == (*di)->fully_qualified_name())
1026 dimsize = it->second;
1027 }
1028
1029 // Define dimension.
1030 int g_dimid = -1;
1031 stax = nc_def_dim(grp_id, (*di)->name().c_str(), dimsize, &g_dimid);
1032 if (stax != NC_NOERR)
1033 FONcUtils::handle_error(stax, "File out netcdf, unable to define dimension: " + _localfile, __FILE__,
1034 __LINE__);
1035 // Save this dimension ID in a map.
1036 fdimname_to_id[(*di)->fully_qualified_name()] = g_dimid;
1037 }
1038
1039 Constructor::Vars_iter vi = grp->var_begin();
1040 Constructor::Vars_iter ve = grp->var_end();
1041
1042 vector < FONcBaseType * > fonc_vars_in_grp;
1043
1044 // If the global_dio_flag is false, we cannot do direct IO for this file at all. No need to check every variable.
1045 // This is necessary since direct IO cannot apply to the current existing dmrpp files in the earth data cloud. KY 2023-12-11
1046 if (global_dio_flag == false) {
1047 for (; vi != ve; vi++) {
1048 if ((*vi)->send_p()) {
1049 BaseType *v = *vi;
1050
1051 BESDEBUG(MODULE, prolog << "Converting variable '" << v->name() << "'" << endl);
1052
1053 // This is a factory class call, and 'fg' is specialized for 'v'
1054 //FONcBaseType *fb = FONcUtils::convert(v,FONcTransform::_returnAs,FONcRequestHandler::classic_model);
1055 FONcBaseType *fb = FONcUtils::convert(v, FONC_RETURN_AS_NETCDF4, false, fdimname_to_id, rds_nums);
1056
1057 fonc_vars_in_grp.push_back(fb);
1058
1059 // This is needed to avoid the memory leak.
1060 _total_fonc_vars_in_grp.push_back(fb);
1061
1062 vector <string> embed;
1063 // This call sets d_is_dap4 to true, and d_is_dap4_group to true. jhrg 2/14/24
1064 fb->convert(embed, true, true);
1065 }
1066 }
1067 }
1068 else {
1069 for (; vi != ve; vi++) {
1070 if ((*vi)->send_p()) {
1071 BaseType *v = *vi;
1072
1073 BESDEBUG(MODULE, prolog << "Converting variable '" << v->name() << "'" << endl);
1074
1075 if (v->type() == dods_array_c) {
1076 auto t_a = dynamic_cast<Array *>(v);
1077 if (t_a->get_dio_flag())
1078 set_constraint_var_dio_flag(t_a);
1079
1080 }
1081
1082 // This is a factory class call, and 'fg' is specialized for 'v'
1083 //FONcBaseType *fb = FONcUtils::convert(v,FONcTransform::_returnAs,FONcRequestHandler::classic_model);
1084 FONcBaseType *fb = FONcUtils::convert(v, FONC_RETURN_AS_NETCDF4, false, fdimname_to_id, rds_nums);
1085
1086 fonc_vars_in_grp.push_back(fb);
1087
1088 // This is needed to avoid the memory leak.
1089 _total_fonc_vars_in_grp.push_back(fb);
1090
1091 vector <string> embed;
1092 // This call sets d_is_dap4 to true, and d_is_dap4_group to true. jhrg 2/14/24
1093 fb->convert(embed, true, true);
1094 }
1095 }
1096 }
1097
1098#if !NDEBUG
1099 if (grp->grp_begin() == grp->grp_end())
1100 BESDEBUG(MODULE, prolog << "No group" << endl);
1101 else
1102 BESDEBUG(MODULE, prolog << "Has group" << endl);
1103#endif
1104
1105
1106 try {
1107 // Here we will be defining the variables of the netcdf and
1108 // adding attributes. To do this we must be in define mode.
1109 //nc_redef(_ncid);
1110
1111 vector<FONcBaseType *>::iterator i = fonc_vars_in_grp.begin();
1112 vector<FONcBaseType *>::iterator e = fonc_vars_in_grp.end();
1113 for (; i != e; i++) {
1114 FONcBaseType *fbt = *i;
1115 BESDEBUG(MODULE, prolog << "Defining variable: " << fbt->name() << endl);
1116 //fbt->set_is_dap4(true);
1117 fbt->define(grp_id);
1118 }
1119
1120 bool is_netCDF_enhanced = false;
1121 if (FONcTransform::_returnAs == FONC_RETURN_AS_NETCDF4 && FONcRequestHandler::classic_model == false)
1122 is_netCDF_enhanced = true;
1123
1124
1125 bool add_attr = true;
1126
1127 // Only the root attribute may be ignored.
1128 if (FONcRequestHandler::no_global_attrs == true && is_root_grp == true)
1129 add_attr = false;
1130
1131 if (true == add_attr) {
1132 D4Attributes *d4_attrs = grp->attributes();
1133 BESDEBUG(MODULE, prolog << "Adding Group Attributes" << endl);
1134 // add dap4 group attributes.
1135 FONcAttributes::add_dap4_attributes(grp_id, NC_GLOBAL, d4_attrs, "", "", is_netCDF_enhanced);
1136 // *** Add the json history here
1137 }
1138
1139 // Write every variable in this group.
1140 i = fonc_vars_in_grp.begin();
1141 e = fonc_vars_in_grp.end();
1142 for (; i != e; i++) {
1143 FONcBaseType *fbt = *i;
1144 RequestServiceTimer::TheTimer()->throw_if_timeout_expired(prolog + "ERROR: bes-timeout expired before transmitting: " + fbt->name() , __FILE__, __LINE__);
1145 BESDEBUG(MODULE, prolog << "Writing data for variable: " << fbt->name() << endl);
1146 //fbt->write(_ncid);
1147 fbt->write(grp_id);
1148 }
1149
1150 // Now handle all the child groups.
1151 for (D4Group::groupsIter gi = grp->grp_begin(), ge = grp->grp_end(); gi != ge; ++gi) {
1152 BESDEBUG(MODULE, prolog << "In group: " << (*gi)->name() << endl);
1153 transform_dap4_group(*gi, false, grp_id, fdimname_to_id, rds_nums);
1154 }
1155
1156 }
1157 catch (BESError &e) {
1158 (void) nc_close(_ncid); // ignore the error at this point
1159 throw;
1160 }
1161 BESDEBUG(MODULE, prolog << "END" << endl);
1162}
1163
1164
1165// Group support is only on when netCDF-4 is in enhanced model and there are groups in the DMR.
1166bool FONcTransform::check_group_support() {
1167 if (FONC_RETURN_AS_NETCDF4 == FONcTransform::_returnAs && false == FONcRequestHandler::classic_model &&
1168 (_dmr->root()->grp_begin() != _dmr->root()->grp_end()))
1169 return true;
1170 else
1171 return false;
1172}
1173
1174// Generate the final group list in the netCDF-4 file. Empty groups and their attributes will be removed.
1175void FONcTransform::gen_included_grp_list(D4Group *grp) {
1176 bool grp_has_var = false;
1177 if (grp) {
1178 BESDEBUG(MODULE, prolog << "Processing D4 Group: " << grp->name() << " fullpath: " << grp->FQN() << endl);
1179
1180 if (grp->var_begin() != grp->var_end()) {
1181
1182 BESDEBUG(MODULE, prolog << "Has child variables" << endl);
1183 Constructor::Vars_iter vi = grp->var_begin();
1184 Constructor::Vars_iter ve = grp->var_end();
1185
1186 for (; vi != ve; vi++) {
1187
1188 // This variable is selected(in the local constraints).
1189 if ((*vi)->send_p()) {
1190 grp_has_var = true;
1191
1192 //If a var in this group is selected, we need to include this group in the netcdf-4 file.
1193 //We always include root attributes, so no need to obtain grp_names for the root.
1194 if (grp->FQN() != "/")
1195 _included_grp_names.insert(grp->FQN());
1196 break;
1197 }
1198 }
1199 }
1200 // Loop through the subgroups to build up the list.
1201 for (D4Group::groupsIter gi = grp->grp_begin(), ge = grp->grp_end(); gi != ge; ++gi) {
1202 BESDEBUG(MODULE, prolog << "Obtain included groups - group name: " << (*gi)->name() << endl);
1203 gen_included_grp_list(*gi);
1204 }
1205 }
1206
1207 // If this group is in the final list, all its ancestors(except root, since it is always selected),should also be included.
1208 if (grp_has_var == true) {
1209 D4Group *temp_grp = grp;
1210 while (temp_grp) {
1211 if (temp_grp->get_parent()) {
1212 temp_grp = static_cast<D4Group *>(temp_grp->get_parent());
1213 if (temp_grp->FQN() != "/")
1214 _included_grp_names.insert(temp_grp->FQN());
1215 }
1216 else
1217 temp_grp = 0;
1218 }
1219 }
1220
1221}
1222
1223void FONcTransform::check_and_obtain_dimensions(D4Group *grp, bool is_root_grp) {
1224
1225 // We may not need to do this way,it may overkill.
1226 bool included_grp = false;
1227
1228 if (_dmr->get_ce_empty())
1229 included_grp = true;
1230 // Always include the root attributes.
1231 else if (is_root_grp == true)
1232 included_grp = true;
1233 else {
1234 // Check if this group is in the group list kept in the file.
1235 set<string>::iterator iset;
1236 if (_included_grp_names.find(grp->FQN()) != _included_grp_names.end())
1237 included_grp = true;
1238 }
1239
1240 if (included_grp == true)
1241 check_and_obtain_dimensions_internal(grp);
1242}
1243
1244void FONcTransform::check_and_obtain_dimensions_internal(D4Group *grp) {
1245
1246 // Remember the Group Fully Qualified dimension Name and the corresponding dimension size.
1247 D4Dimensions *grp_dims = grp->dims();
1248 if (grp_dims) {
1249 for (D4Dimensions::D4DimensionsIter di = grp_dims->dim_begin(), de = grp_dims->dim_end(); di != de; ++di) {
1250
1251#if !NDEBUG
1252 BESDEBUG(MODULE, prolog << "Check dimensions" << endl);
1253 BESDEBUG(MODULE, prolog << "Dim name is: " << (*di)->name() << endl);
1254 BESDEBUG(MODULE, prolog << "Dim size is: " << (*di)->size() << endl);
1255 BESDEBUG(MODULE, prolog << "Fully qualfied dim name: " << (*di)->fully_qualified_name() << endl);
1256#endif
1257 int64_t dimsize = (*di)->size();
1258 if ((*di)->constrained()) {
1259 dimsize = ((*di)->c_stop() - (*di)->c_start()) / (*di)->c_stride() + 1;
1260
1261 }
1262 GFQN_dimname_to_dimsize[(*di)->fully_qualified_name()] = dimsize;
1263 }
1264 }
1265
1266 // The size of DAP4 dimension needs to be updated if the dimension size of a variable with the same dimension is
1267 // different. So we also need to remember the Variable FQN dimension name and size.
1268 // Check section 8.6.2 of DAP4 specification(https://docs.opendap.org/index.php/DAP4:_Specification_Volume_1)
1269 Constructor::Vars_iter vi = grp->var_begin();
1270 Constructor::Vars_iter ve = grp->var_end();
1271 for (; vi != ve; vi++) {
1272 if ((*vi)->send_p()) {
1273 if ((*vi)->is_vector_type()) {
1274 Array *t_a = dynamic_cast<Array *>(*vi);
1275 Array::Dim_iter dim_i = t_a->dim_begin();
1276 Array::Dim_iter dim_e = t_a->dim_end();
1277 for (; dim_i != dim_e; dim_i++) {
1278 if ((*dim_i).name != "") {
1279 D4Dimension *d4dim = t_a->dimension_D4dim(dim_i);
1280 if (d4dim) {
1281 BESDEBUG(MODULE, prolog << "Check dim- dim name is: " << d4dim->name() << endl);
1282 BESDEBUG(MODULE, prolog << "Check dim- dim size is: " << d4dim->size() << endl);
1283 BESDEBUG(MODULE, prolog << "Check dim- fully_qualfied_dim name is: "
1284 << d4dim->fully_qualified_name() << endl);
1285
1286 int64_t dimsize = t_a->dimension_size_ll(dim_i, true);
1287#if !NDEBUG
1288 BESDEBUG(MODULE, prolog << "Check dim- final dim size is: " << dimsize << endl);
1289#endif
1290 pair<map<string, int64_t>::iterator, bool> ret_it;
1291 ret_it = VFQN_dimname_to_dimsize.insert(
1292 pair<string, int64_t>(d4dim->fully_qualified_name(), dimsize));
1293 if (ret_it.second == false && ret_it.first->second != dimsize) {
1294 string err = "fileout_netcdf-4: dimension found with the same name, but different size";
1295 throw BESInternalError(err, __FILE__, __LINE__);
1296 }
1297 //VFQN_dimname_to_dimsize[d4dim->fully_qualified_name()] = dimsize;
1298 }
1299 else
1300 throw BESInternalError("Has dimension name but D4 dimension is NULL", __FILE__, __LINE__);
1301 }
1302 // No need to handle the case when the dimension name doesn't exist. This will be handled in FONcArray.cc.
1303 // else { }
1304 }
1305 }
1306 }
1307 }
1308
1309#if !NDEBUG
1310 map<string, int64_t>::iterator it;
1311 for (it = GFQN_dimname_to_dimsize.begin(); it != GFQN_dimname_to_dimsize.end(); ++it) {
1312 BESDEBUG(MODULE, prolog << "GFQN dim name is: " << it->first << endl);
1313 BESDEBUG(MODULE, prolog << "GFQN dim size is: " << it->second << endl);
1314 }
1315
1316 for (it = VFQN_dimname_to_dimsize.begin(); it != VFQN_dimname_to_dimsize.end(); ++it) {
1317 BESDEBUG(MODULE, prolog << "VFQN dim name is: " << it->first << endl);
1318 BESDEBUG(MODULE, prolog << "VFQN dim size is: " << it->second << endl);
1319 }
1320
1321#endif
1322
1323 // Go through all the descendent groups.
1324 for (D4Group::groupsIter gi = grp->grp_begin(), ge = grp->grp_end(); gi != ge; ++gi) {
1325 BESDEBUG(MODULE,prolog << "In group: " << (*gi)->name() << endl);
1326 check_and_obtain_dimensions(*gi, false);
1327 }
1328
1329}
1330void FONcTransform::set_constraint_var_dio_flag(libdap::Array* t_a) const {
1331
1332
1333 // The last check to see if the direct io can be done is to check if
1334 // this array is subset. If yes, we cannot use direct IO.
1335
1336 bool partial_subset_array = false;
1337 Array::Dim_iter di = t_a->dim_begin();
1338 Array::Dim_iter de = t_a->dim_end();
1339 for (; di != de; di++) {
1340 if (t_a->dimension_size_ll(di,true) != t_a->dimension_size_ll(di, false)) {
1341 partial_subset_array = true;
1342 break;
1343 }
1344 }
1345 if (partial_subset_array)
1346 t_a->set_dio_flag(false);
1347
1348}
1349
1350
1351void FONcTransform::set_constraint_var_dio_flag(libdap::BaseType* bt) const{
1352
1353 if (bt->type() == dods_array_c) {
1354
1355 auto t_a=dynamic_cast<Array *>(bt);
1356
1357 // The last check to see if the direct io can be done is to check if
1358 // this array is subset. If yes, we cannot use direct IO.
1359 if (t_a->get_dio_flag()) {
1360
1361 bool partial_subset_array = false;
1362 Array::Dim_iter di = t_a->dim_begin();
1363 Array::Dim_iter de = t_a->dim_end();
1364
1365 for (; di != de; di++) {
1366 if (t_a->dimension_size_ll(di,true) != t_a->dimension_size_ll(di, false)) {
1367 partial_subset_array = true;
1368 break;
1369 }
1370 }
1371 if (partial_subset_array)
1372 t_a->set_dio_flag(false);
1373 }
1374 }
1375}
1376
1377bool FONcTransform::check_reduce_dim() {
1378
1379 bool ret_value = true;
1380 D4Group *root_grp = _dmr->root();
1381 ret_value = check_reduce_dim_internal(root_grp);
1382 return ret_value;
1383}
1384
1385bool FONcTransform::check_reduce_dim_internal(D4Group*grp) {
1386
1387 bool ret_value = true;
1388 D4Dimensions *grp_dims = grp->dims();
1389
1390 // Check DAP4 dimensions
1391 for (D4Dimensions::D4DimensionsIter di = grp_dims->dim_begin(), de = grp_dims->dim_end(); di != de; ++di) {
1392 if((*di)->name().empty() == false) {
1393 ret_value = false;
1394 break;
1395 }
1396 }
1397
1398 // Check DAP4 variables
1399 if (ret_value) {
1400 for (const auto &var:grp->variables()) {
1401 if (var->send_p()) {
1402 ret_value = check_var_dim(var);
1403 if (ret_value == false)
1404 break;
1405 }
1406 }
1407 }
1408 // Check the children groups
1409 if (ret_value) {
1410 for (D4Group::groupsIter gi = grp->grp_begin(), ge = grp->grp_end(); gi != ge; ++gi) {
1411 ret_value = check_reduce_dim_internal(*gi);
1412 if (ret_value ==false)
1413 break;
1414 }
1415 }
1416 return ret_value;
1417
1418}
1419
1420bool FONcTransform::check_var_dim(BaseType *var) {
1421
1422 bool ret_value = true;
1423
1424 if (var->type() == dods_array_c) {
1425
1426 auto t_a = dynamic_cast<Array *>(var);
1427 Array::Dim_iter dim_i = t_a->dim_begin();
1428 Array::Dim_iter dim_e = t_a->dim_end();
1429 for (; dim_i != dim_e; dim_i++) {
1430 if ((*dim_i).name != "") {
1431 ret_value = false;
1432 break;
1433 }
1434 }
1435 }
1436 return ret_value;
1437}
1438
1439void FONcTransform::build_reduce_dim() {
1440
1441 D4Group *root_grp = _dmr->root();
1442 build_reduce_dim_internal(root_grp, root_grp);
1443}
1444
1445void FONcTransform::build_reduce_dim_internal(D4Group *grp, D4Group *root_grp) {
1446
1447 for (auto &var:grp->variables()) {
1448
1449 if (var->type() == dods_array_c && var->send_p()) {
1450 auto t_a = dynamic_cast<Array *>(var);
1451
1452 unordered_map<int64_t,int> local_dsize_count;
1453
1454 Array::Dim_iter dim_i = t_a->dim_begin();
1455 Array::Dim_iter dim_e = t_a->dim_end();
1456 for (; dim_i != dim_e; dim_i++) {
1457
1458 if ((*dim_i).name == "") {
1459
1460 int64_t dimsize = t_a->dimension_size_ll(dim_i, true);
1461
1462 // We need to update the number of occurences this dim size is used in this array.
1463 bool local_dsize_found = false;
1464 if(local_dsize_count.find(dimsize)!=local_dsize_count.end()) {
1465 int prev_count = local_dsize_count[dimsize];
1466 local_dsize_count[dimsize] = prev_count +1;
1467 local_dsize_found = true;
1468 }
1469 else
1470 local_dsize_count[dimsize] = 1;
1471
1472 bool dim_name_exist = false;
1473 auto it_sn=dimsize_to_dup_dimnames.find(dimsize);
1474 if (it_sn !=dimsize_to_dup_dimnames.end()) {
1475 vector<string>temp_dimnames = dimsize_to_dup_dimnames[dimsize];
1476 if (local_dsize_found) {
1477
1478 int temp_local_dsize_count = local_dsize_count[dimsize];
1479
1480 // Now we need to create a new dim name for this dimension
1481 // since for this size, the unique dimension names are used up.
1482 if (temp_local_dsize_count > temp_dimnames.size()) {
1483 string dim_name_suffix= to_string(reduced_dim_num);
1484 (*dim_i).name ="dim" + dim_name_suffix;
1485 reduced_dim_num++;
1486 // Update the global map
1487 temp_dimnames.push_back((*dim_i).name);
1488 dimsize_to_dup_dimnames[dimsize]=temp_dimnames;
1489 }
1490 else {//Pick up the earliest created non-used dimension name.
1491 (*dim_i).name = temp_dimnames[temp_local_dsize_count-1];
1492 dim_name_exist = true;
1493 }
1494
1495 }
1496 else { // Use the first created dimension name for this dimension size.
1497 (*dim_i).name = temp_dimnames[0];
1498 dim_name_exist = true;
1499 }
1500 }
1501 else { // This dimension has not been assigned a name in this file so far,assign it.
1502 string dim_name_suffix= to_string(reduced_dim_num);
1503 (*dim_i).name ="dim" + dim_name_suffix;
1504 reduced_dim_num++;
1505 vector<string>temp_dimnames;
1506 temp_dimnames.push_back((*dim_i).name);
1507 dimsize_to_dup_dimnames[dimsize] = temp_dimnames;
1508 }
1509
1510 if (dim_name_exist) {
1511
1512 D4Dimensions *dims = root_grp->dims();
1513 D4Dimension *d4_dim = dims->find_dim((*dim_i).name);
1514 if(d4_dim == nullptr)
1515 throw BESInternalError("D4 dimension cannot be found", __FILE__, __LINE__);
1516 else
1517 (*dim_i).dim= d4_dim;
1518 }
1519 else {
1520 // We need to add D4Dimension and group dimensions.
1521 auto d4_dim0_unique = make_unique<D4Dimension>((*dim_i).name, dimsize);
1522 (*dim_i).dim=d4_dim0_unique.get();
1523
1524 // The DAP4 group needs also to store these dimensions.
1525 D4Dimensions *dims = root_grp->dims();
1526 dims->add_dim_nocopy(d4_dim0_unique.release());
1527 }
1528 }
1529 }
1530 }
1531 }
1532
1533 for (D4Group::groupsIter gi = grp->grp_begin(), ge = grp->grp_end(); gi != ge; ++gi)
1534 build_reduce_dim_internal(*gi,root_grp);
1535
1536}
1537
1538
1548void FONcTransform::dump(ostream &strm) const {
1549 strm << BESIndent::LMarg << "FONcTransform::dump - (" << (void *) this << ")" << endl;
1550 BESIndent::Indent();
1551 strm << BESIndent::LMarg << "ncid = " << _ncid << endl;
1552 strm << BESIndent::LMarg << "temporary file = " << _localfile << endl;
1553 BESIndent::Indent();
1554 vector<FONcBaseType *>::const_iterator i = _fonc_vars.begin();
1555 vector<FONcBaseType *>::const_iterator e = _fonc_vars.end();
1556 for (; i != e; i++) {
1557 FONcBaseType *fbt = *i;
1558 fbt->dump(strm);
1559 }
1560 BESIndent::UnIndent();
1561 BESIndent::UnIndent();
1562}
1563
1564
Cache the results from server functions.
virtual libdap::DDS * get_or_cache_dataset(libdap::DDS *dds, const std::string &constraint)
Return a DDS loaded with data that can be serialized back to a client.
virtual void set_dataset_name(const std::string &_dataset)
Set the dataset pathname.
virtual void split_ce(libdap::ConstraintEvaluator &eval, const std::string &expr="")
virtual std::string get_ce() const
Get the constraint expression.
virtual void set_dap4function(const std::string &_func)
virtual std::string get_dap4ce() const
Get the DAP4 constraint expression.
virtual void set_dap4ce(const std::string &_ce)
virtual void set_ce(std::string _ce)
Represents an OPeNDAP DataDDS DAP2 data object within the BES.
Structure storing information used by the BES to handle the request.
void first_container()
set the container pointer to the first container in the containers list
BESContainer * container
pointer to current container in this interface
Base exception class for the BES with basic string message.
Definition BESError.h:66
exception thrown if internal error encountered
exception thrown if an internal error is found and is fatal to the BES
Represents a specific data type request handler.
Abstract base class representing a specific set of information in response to a request to the BES.
error thrown if there is a user syntax error in the request or any other user error
static void add_dap4_attributes(int ncid, int varid, D4Attributes *d4_attrs, const string &var_name, const string &prepend_attr, bool is_netCDF_enhanced)
add_dap4_attributes
static void add_attributes(int ncid, int varid, AttrTable &attrs, const string &var_name, const string &prepend_attr, bool is_netCDF_enhanced)
helper function for add_attributes
A DAP BaseType with file out netcdf information included.
virtual void define(int ncid)
Define the variable in the netcdf file.
void dump(std::ostream &strm) const override=0
dump the contents of this object to the specified ostream
virtual void dump(ostream &strm) const
dumps information about this transformation object for debugging purposes
virtual void transform_dap2()
Transforms each of the variables of the DataDDS to the NetCDF file.
FONcTransform(BESResponseObject *obj, BESDataHandlerInterface *dhi, const std::string &localfile, const std::string &ncVersion="netcdf")
Constructor that creates transformation object from the specified BESResponseObject object to the spe...
virtual void transform_dap4()
Transforms each of the variables of the DMR to the NetCDF file.
virtual ~FONcTransform()
Destructor.
static void handle_error(int stax, const string &err, const string &file, int line)
handle any netcdf errors
Definition FONcUtils.cc:429
static void reset()
Resets the FONc transformation for a new input and out file.
Definition FONcUtils.cc:70
static string id2netcdf(string in)
convert the provided string to a netcdf allowed identifier.
Definition FONcUtils.cc:87
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...
STL iterator class.
STL iterator class.