OPeNDAP Hyrax Back End Server (BES)  Updated for version 3.8.3
BESStoredDapResultCache.cc
Go to the documentation of this file.
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) 2011 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 //
23 // You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
24 
25 #include "config.h"
26 
27 //#define DODS_DEBUG
28 
29 #include <sys/stat.h>
30 
31 #include <iostream>
32 #ifdef HAVE_TR1_FUNCTIONAL
33 #include <tr1/functional>
34 #endif
35 #include <string>
36 #include <fstream>
37 #include <sstream>
38 
39 #include <DDS.h>
40 #include <DMR.h>
41 #include <DapXmlNamespaces.h>
42 #include <ConstraintEvaluator.h>
43 #include <DDXParserSAX2.h>
44 
45 // These are needed because D4ParserSax2.h does not properly declare
46 // the classes. I think. Check on that... jhrg 3/28/14
47 #include <D4EnumDefs.h>
48 #include <D4Dimensions.h>
49 #include <D4Group.h>
50 
51 #include <D4ParserSax2.h>
52 #include <XDRStreamMarshaller.h>
53 #include <XDRStreamUnMarshaller.h>
54 #include <chunked_istream.h>
55 #include <D4StreamUnMarshaller.h>
56 //<XDRFileUnMarshaller.h>
57 #include <debug.h>
58 #include <mime_util.h> // for last_modified_time() and rfc_822_date()
59 #include <util.h>
60 
62 #include "BESDapResponseBuilder.h"
63 #include "BESInternalError.h"
64 
65 #include "BESUtil.h"
66 #include "TheBESKeys.h"
67 #include "BESDebug.h"
68 
69 #ifdef HAVE_TR1_FUNCTIONAL
70 #define HASH_OBJ std::tr1::hash
71 #else
72 #define HASH_OBJ std::hash
73 #endif
74 
75 #define CRLF "\r\n"
76 #define BES_DATA_ROOT "BES.Data.RootDirectory"
77 #define BES_CATALOG_ROOT "BES.Catalog.catalog.RootDirectory"
78 
79 using namespace std;
80 using namespace libdap;
81 
82 BESStoredDapResultCache *BESStoredDapResultCache::d_instance = 0;
83 const string BESStoredDapResultCache::SUBDIR_KEY = "DAP.StoredResultsCache.subdir";
84 const string BESStoredDapResultCache::PREFIX_KEY = "DAP.StoredResultsCache.prefix";
85 const string BESStoredDapResultCache::SIZE_KEY = "DAP.StoredResultsCache.size";
86 
87 unsigned long BESStoredDapResultCache::getCacheSizeFromConfig(){
88 
89  bool found;
90  string size;
91  unsigned long size_in_megabytes = 0;
92  TheBESKeys::TheKeys()->get_value( SIZE_KEY, size, found ) ;
93  if( found ) {
94  istringstream iss(size);
95  iss >> size_in_megabytes;
96  }
97  else {
98  string msg = "[ERROR] BESStoredDapResultCache::getCacheSize() - The BES Key " + SIZE_KEY + " is not set! It MUST be set to utilize the Stored Result Caching system. ";
99  BESDEBUG("cache", msg << endl);
100  throw BESInternalError(msg , __FILE__, __LINE__);
101  }
102  return size_in_megabytes;
103 }
104 
105 string BESStoredDapResultCache::getSubDirFromConfig(){
106  bool found;
107  string subdir = "";
108  TheBESKeys::TheKeys()->get_value( SUBDIR_KEY, subdir, found ) ;
109 
110  if( !found ) {
111  string msg = "[ERROR] BESStoredDapResultCache::getSubDirFromConfig() - The BES Key " + SUBDIR_KEY + " is not set! It MUST be set to utilize the Stored Result Caching system. ";
112  BESDEBUG("cache", msg << endl);
113  throw BESInternalError(msg , __FILE__, __LINE__);
114  }
115  else {
116  while(*subdir.begin() == '/' && subdir.length()>0){
117  subdir = subdir.substr(1);
118  }
119  // So if it's value is "/" or the empty string then the subdir will default to the root
120  // directory of the BES data system.
121  }
122 
123  return subdir;
124 }
125 
126 
127 string BESStoredDapResultCache::getResultPrefixFromConfig(){
128  bool found;
129  string prefix = "";
130  TheBESKeys::TheKeys()->get_value( PREFIX_KEY, prefix, found ) ;
131  if( found ) {
132  prefix = BESUtil::lowercase( prefix ) ;
133  }
134  else {
135  string msg = "[ERROR] BESStoredDapResultCache::getResultPrefix() - The BES Key " + PREFIX_KEY + " is not set! It MUST be set to utilize the Stored Result Caching system. ";
136  BESDEBUG("cache", msg << endl);
137  throw BESInternalError(msg , __FILE__, __LINE__);
138  }
139 
140  return prefix;
141 }
142 
143 
144 string BESStoredDapResultCache::getBesDataRootDirFromConfig(){
145  bool found;
146  string cacheDir = "";
147  TheBESKeys::TheKeys()->get_value( BES_CATALOG_ROOT, cacheDir, found ) ;
148  if( !found ) {
149  TheBESKeys::TheKeys()->get_value( BES_DATA_ROOT, cacheDir, found ) ;
150  if( !found ) {
151  string msg = ((string)"[ERROR] BESStoredDapResultCache::getStoredResultsDir() - Neither the BES Key ") + BES_CATALOG_ROOT +
152  "or the BES key " + BES_DATA_ROOT + " have been set! One MUST be set to utilize the Stored Result Caching system. ";
153  BESDEBUG("cache", msg << endl);
154  throw BESInternalError(msg , __FILE__, __LINE__);
155  }
156  }
157  return cacheDir;
158 
159 }
160 
161 string BESStoredDapResultCache::assemblePath(const string &firstPart, const string &secondPart, bool addLeadingSlash){
162 
163  //BESDEBUG("cache", "BESStoredDapResultCache::assemblePath() - BEGIN" << endl);
164  //BESDEBUG("cache", "BESStoredDapResultCache::assemblePath() - firstPart: "<< firstPart << endl);
165  //BESDEBUG("cache", "BESStoredDapResultCache::assemblePath() - secondPart: "<< secondPart << endl);
166 
167  string firstPathFragment = firstPart;
168  string secondPathFragment = secondPart;
169 
170 
171  if(addLeadingSlash){
172  if(*firstPathFragment.begin() != '/')
173  firstPathFragment = "/" + firstPathFragment;
174  }
175 
176  // make sure there are not multiple slashes at the end of the first part...
177  while(*firstPathFragment.rbegin() == '/' && firstPathFragment.length()>0){
178  firstPathFragment = firstPathFragment.substr(0,firstPathFragment.length()-1);
179  // BESDEBUG("cache", "BESStoredDapResultCache::assemblePath() - firstPathFragment: "<< firstPathFragment << endl);
180  }
181 
182  // make sure first part ends with a "/"
183  if(*firstPathFragment.rbegin() != '/'){
184  firstPathFragment += "/";
185  }
186  //BESDEBUG("cache", "BESStoredDapResultCache::assemblePath() - firstPathFragment: "<< firstPathFragment << endl);
187 
188  // make sure second part does not BEGIN with a slash
189  while(*secondPathFragment.begin() == '/' && secondPathFragment.length()>0){
190  secondPathFragment = secondPathFragment.substr(1);
191  }
192 
193  //BESDEBUG("cache", "BESStoredDapResultCache::assemblePath() - secondPathFragment: "<< secondPathFragment << endl);
194 
195  string newPath = firstPathFragment + secondPathFragment;
196 
197  //BESDEBUG("cache", "BESStoredDapResultCache::assemblePath() - newPath: "<< newPath << endl);
198  //BESDEBUG("cache", "BESStoredDapResultCache::assemblePath() - END" << endl);
199 
200  return newPath;
201 }
202 
203 BESStoredDapResultCache::BESStoredDapResultCache(){
204  BESDEBUG("cache", "BESStoredDapResultCache::BESStoredDapResultCache() - BEGIN" << endl);
205 
206  d_storedResultsSubdir = getSubDirFromConfig();
207  d_dataRootDir = getBesDataRootDirFromConfig();
208  string resultsDir = assemblePath(d_dataRootDir,d_storedResultsSubdir);
209 
210  d_resultFilePrefix = getResultPrefixFromConfig();
211  d_maxCacheSize = getCacheSizeFromConfig();
212 
213  BESDEBUG("cache", "BESStoredDapResultCache() - Stored results cache configuration params: " << resultsDir << ", " << d_resultFilePrefix << ", " << d_maxCacheSize << endl);
214 
215  initialize(resultsDir, d_resultFilePrefix, d_maxCacheSize);
216 
217  BESDEBUG("cache", "BESStoredDapResultCache::BESStoredDapResultCache() - END" << endl);
218 }
219 
220 
224 BESStoredDapResultCache::BESStoredDapResultCache( const string &data_root_dir, const string &stored_results_subdir, const string &result_file_prefix, unsigned long long max_cache_size){
225 
226  d_storedResultsSubdir = stored_results_subdir;
227  d_dataRootDir = data_root_dir;
228  d_resultFilePrefix = result_file_prefix;
229  d_maxCacheSize = max_cache_size;
230  initialize(assemblePath(d_dataRootDir,stored_results_subdir), d_resultFilePrefix, d_maxCacheSize);
231 }
232 
233 
235 BESStoredDapResultCache::get_instance(const string &data_root_dir, const string &stored_results_subdir, const string &result_file_prefix, unsigned long long max_cache_size)
236 {
237  if (d_instance == 0){
238  if(dir_exists(data_root_dir)){
239  try {
240  d_instance = new BESStoredDapResultCache(data_root_dir, stored_results_subdir, result_file_prefix, max_cache_size);
241  }
242  catch(BESInternalError &bie){
243  BESDEBUG("cache", "[ERROR] BESStoredDapResultCache::get_instance(): Failed to obtain cache! msg: " << bie.get_message() << endl);
244  }
245  }
246  }
247  return d_instance;
248 }
249 
255 {
256  if (d_instance == 0) {
257  try {
258  d_instance = new BESStoredDapResultCache();
259  }
260  catch(BESInternalError &bie){
261  BESDEBUG("cache", "[ERROR] BESStoredDapResultCache::get_instance(): Failed to obtain cache! msg: " << bie.get_message() << endl);
262  }
263  }
264 
265  return d_instance;
266 }
267 
268 
269 void BESStoredDapResultCache::delete_instance() {
270  BESDEBUG("cache","BESStoredDapResultCache::delete_instance() - Deleting singleton BESStoredDapResultCache instance." << endl);
271  delete d_instance;
272  d_instance = 0;
273 }
274 
284 bool BESStoredDapResultCache::is_valid(const string &cache_file_name, const string &dataset)
285 {
286  // If the cached response is zero bytes in size, it's not valid.
287  // (hmmm...)
288 
289  off_t entry_size = 0;
290  time_t entry_time = 0;
291  struct stat buf;
292  if (stat(cache_file_name.c_str(), &buf) == 0) {
293  entry_size = buf.st_size;
294  entry_time = buf.st_mtime;
295  }
296  else {
297  return false;
298  }
299 
300  if (entry_size == 0)
301  return false;
302 
303  time_t dataset_time = entry_time;
304  if (stat(dataset.c_str(), &buf) == 0) {
305  dataset_time = buf.st_mtime;
306  }
307 
308  // Trick: if the d_dataset is not a file, stat() returns error and
309  // the times stay equal and the code uses the cache entry.
310 
311  // TODO Fix this so that the code can get a LMT from the correct
312  // handler.
313  if (dataset_time > entry_time)
314  return false;
315 
316  return true;
317 }
318 
330 bool BESStoredDapResultCache::read_dap2_data_from_cache(const string &cache_file_name, DDS *fdds)
331 {
332  BESDEBUG("cache", "BESStoredDapResultCache::read_dap2_data_from_cache() - Opening cache file: " << cache_file_name << endl);
333 
334  int fd = 1;
335 
336  try {
337  if (get_read_lock(cache_file_name, fd)) {
338 
339  ifstream data(cache_file_name.c_str());
340 
341  // Rip off the MIME headers from the response if they are present
342  string mime = get_next_mime_header(data);
343  while (!mime.empty()) {
344  mime = get_next_mime_header(data);
345  }
346 
347  // Parse the DDX; throw an exception on error.
348  DDXParser ddx_parser(fdds->get_factory());
349 
350  // Read the MPM boundary and then read the subsequent headers
351  string boundary = read_multipart_boundary(data);
352  BESDEBUG("cache", "BESStoredDapResultCache::read_dap2_data_from_cache() - MPM Boundary: " << boundary << endl);
353 
354  read_multipart_headers(data, "text/xml", dods_ddx);
355 
356  BESDEBUG("cache", "BESStoredDapResultCache::read_dap2_data_from_cache() - Read the multipart haeaders" << endl);
357 
358  // Parse the DDX, reading up to and including the next boundary.
359  // Return the CID for the matching data part
360  string data_cid;
361  try {
362  ddx_parser.intern_stream(data, fdds, data_cid, boundary);
363  BESDEBUG("cache", "BESStoredDapResultCache::read_dap2_data_from_cache() - Dataset name: " << fdds->get_dataset_name() << endl);
364  }
365  catch(Error &e) {
366  BESDEBUG("cache", "BESStoredDapResultCache::read_dap2_data_from_cache() - DDX Parser Error: " << e.get_error_message() << endl);
367  throw;
368  }
369 
370  // Munge the CID into something we can work with
371  BESDEBUG("cache", "BESStoredDapResultCache::read_dap2_data_from_cache() - Data CID (before): " << data_cid << endl);
372  data_cid = cid_to_header_value(data_cid);
373  BESDEBUG("cache", "BESStoredDapResultCache::read_dap2_data_from_cache() - Data CID (after): " << data_cid << endl);
374 
375  // Read the data part's MPM part headers (boundary was read by
376  // DDXParse::intern)
377  read_multipart_headers(data, "application/octet-stream", dods_data_ddx, data_cid);
378 
379  // Now read the data
380 
381  // XDRFileUnMarshaller um(data);
382  XDRStreamUnMarshaller um(data);
383  for (DDS::Vars_iter i = fdds->var_begin(); i != fdds->var_end(); i++) {
384  (*i)->deserialize(um, fdds);
385  }
386 
387  data.close();
388  unlock_and_close(cache_file_name /* was fd */);
389  return true;
390  }
391  else {
392  BESDEBUG( "cache", "BESStoredDapResultCache - The requested file does not exist. File: " + cache_file_name);
393 
394  return false;
395  }
396  }
397  catch (...) {
398  BESDEBUG("cache", "BESStoredDapResultCache::read_dap4_data_from_cache() - caught exception, unlocking cache and re-throw." << endl );
399  // I think this call is not needed. jhrg 10/23/12
400  if (fd != -1)
401  unlock_and_close(cache_file_name /* was fd */);
402  throw;
403  }
404 }
405 
417 bool BESStoredDapResultCache::read_dap4_data_from_cache(const string &cache_file_name, libdap::DMR *dmr)
418 {
419  BESDEBUG("cache", "BESStoredDapResultCache::read_dap4_data_from_cache() - BEGIN" << endl);
420 
421 
422  int fd = 1;
423 
424  try {
425  if (get_read_lock(cache_file_name, fd)) {
426  BESDEBUG("cache", "BESStoredDapResultCache::read_dap4_data_from_cache() - Opening cache file: " << cache_file_name << endl);
427  fstream in(cache_file_name.c_str(), ios::in|ios::binary);
428 
429  // Gobble up the response's initial set of MIME headers. Normally
430  // a client would extract information from these headers.
431  // NOTE - I am dumping this call because it basically just
432  // slurps up lines until it finds a blank line, regardless of what the
433  // lines actually have in the. So basically if the stream DOESN't have
434  // a mime header then this call will read (and ignore) the entire
435  // XML encoding of the DMR. doh.
436  // remove_mime_header(in);
437 
438  chunked_istream cis(in, CHUNK_SIZE);
439 
440  bool debug = BESDebug::IsSet( "parser" );
441 
442  // parse the DMR, stopping when the boundary is found.
443  // force chunk read
444  // get chunk size
445  int chunk_size = cis.read_next_chunk();
446 
447  BESDEBUG("cache", "BESStoredDapResultCache::read_dap4_data_from_cache() - First chunk_size: " << chunk_size << endl);
448 
449 
450  if(chunk_size == EOF){
451  throw InternalErr(__FILE__, __LINE__, "BESStoredDapResultCache::read_dap4_data_from_cache() - Failed to read first chunk from file. Chunk size = EOF (aka " + libdap::long_to_string(EOF) + ")");
452  }
453 
454  // get chunk
455  char chunk[chunk_size];
456  cis.read(chunk, chunk_size);
457  BESDEBUG("cache", "BESStoredDapResultCache::read_dap4_data_from_cache() - Read first chunk." << endl);
458 
459  // parse char * with given size
460  D4ParserSax2 parser;
461  // '-2' to discard the CRLF pair
462  parser.intern(chunk, chunk_size-2, dmr, debug);
463  BESDEBUG("cache", "BESStoredDapResultCache::read_dap4_data_from_cache() - Parsed first chunk." << endl);
464 
465  D4StreamUnMarshaller um(cis, cis.twiddle_bytes());
466 
467  dmr->root()->deserialize(um, *dmr);
468  BESDEBUG("cache", "BESStoredDapResultCache::read_dap4_data_from_cache() - Deserialized data." << endl);
469 
470  BESDEBUG("cache", "BESStoredDapResultCache::read_dap4_data_from_cache() - END" << endl);
471 
472  in.close();
473  unlock_and_close(cache_file_name /* was fd */);
474 
475  return true;
476 
477  }
478  else {
479  BESDEBUG( "cache", "BESStoredDapResultCache - The requested file does not exist. File: " + cache_file_name);
480 
481  return false;
482 
483  }
484  }
485  catch (...) {
486  BESDEBUG("cache", "BESStoredDapResultCache::read_dap4_data_from_cache() - caught exception, unlocking cache and re-throw." << endl );
487  // I think this call is not needed. jhrg 10/23/12
488  if (fd != -1)
489  unlock_and_close(cache_file_name /* was fd */);
490  throw;
491  }
492 }
493 
498 DDS *
499 BESStoredDapResultCache::get_cached_dap2_data_ddx(const string &cache_file_name, BaseTypeFactory *factory, const string &filename)
500 {
501  BESDEBUG("cache", "Reading cache for " << cache_file_name << endl);
502 
503  DDS *fdds = new DDS(factory);
504 
505 
506  if(read_dap2_data_from_cache(cache_file_name, fdds)){
507 
508 
509  fdds->filename(filename) ;
510  //fdds->set_dataset_name( "function_result_" + name_path(filename) ) ;
511 
512  BESDEBUG("cache", "DDS Filename: " << fdds->filename() << endl);
513  BESDEBUG("cache", "DDS Dataset name: " << fdds->get_dataset_name() << endl);
514 
515  fdds->set_factory( 0 ) ;
516 
517  // mark everything as read. and send. That is, make sure that when a response
518  // is retrieved from the cache, all of the variables are marked as to be sent
519  DDS::Vars_iter i = fdds->var_begin();
520  while(i != fdds->var_end()) {
521  (*i)->set_read_p( true );
522  (*i++)->set_send_p(true);
523  }
524 
525  return fdds;
526  }
527  else {
528  delete fdds;
529  return 0;
530  }
531 
532 
533 }
534 
535 
540 DMR *
541 BESStoredDapResultCache::get_cached_dap4_data(const string &cache_file_name, libdap::D4BaseTypeFactory *factory, const string &filename)
542 {
543  BESDEBUG("cache", "BESStoredDapResultCache::get_cached_dap4_data() - Reading cache for " << cache_file_name << endl);
544 
545  DMR *fdmr = new DMR(factory);
546 
547  BESDEBUG("cache", "BESStoredDapResultCache::get_cached_dap4_data() - DMR Filename: " << fdmr->filename() << endl);
548  fdmr->set_filename(filename) ;
549 
550  if(read_dap4_data_from_cache(cache_file_name, fdmr)){
551  BESDEBUG("cache", "BESStoredDapResultCache::get_cached_dap4_data() - DMR Dataset name: " << fdmr->name() << endl);
552 
553  fdmr->set_factory( 0 ) ;
554 
555  // mark everything as read. and send. That is, make sure that when a response
556  // is retrieved from the cache, all of the variables are marked as to be sent
557  fdmr->root()->set_send_p(true);
558  fdmr->root()->set_read_p(true);
559 
560  return fdmr;
561  }
562 
563  return 0;
564 }
565 
566 #if 0
567 
572 string BESStoredDapResultCache::store_dap2_result(DDS &dds, const string &constraint, BESDapResponseBuilder *rb, ConstraintEvaluator *eval)
573 {
574  BESDEBUG("cache", "BESStoredDapResultCache::store_dap2_result() - BEGIN" << endl );
575  // These are used for the cached or newly created DDS object
576  BaseTypeFactory factory;
577  DDS *fdds;
578 
579  // Get the cache filename for this thing. Do not use the default
580  // name mangling; instead use what build_cache_file_name() does.
581  string local_id = get_stored_result_local_id(dds.filename(), constraint);
582  BESDEBUG("cache", "BESStoredDapResultCache::store_dap2_result() - local_id: "<< local_id << endl );
583  string cache_file_name = get_cache_file_name(local_id, /*mangle*/false);
584  BESDEBUG("cache", "BESStoredDapResultCache::store_dap2_result() - cache_file_name: "<< cache_file_name << endl );
585  int fd;
586  try {
587  // If the object in the cache is not valid, remove it. The read_lock will
588  // then fail and the code will drop down to the create_and_lock() call.
589  // is_valid() tests for a non-zero object and for d_dateset newer than
590  // the cached object.
591  if (!is_valid(cache_file_name, dds.filename()))
592  purge_file(cache_file_name);
593 
594  if (get_read_lock(cache_file_name, fd)) {
595  BESDEBUG("cache", "BESStoredDapResultCache::store_dap2_result() - function ce (change)- cached hit: " << cache_file_name << endl);
596  fdds = get_cached_dap2_data_ddx(cache_file_name, &factory, dds.filename());
597  }
598  else if (create_and_lock(cache_file_name, fd)) {
599  // If here, the cache_file_name could not be locked for read access;
600  // try to build it. First make an empty file and get an exclusive lock on it.
601  BESDEBUG("cache", "BESStoredDapResultCache::store_dap2_result() - function ce - caching " << cache_file_name << ", constraint: " << constraint << endl);
602 
603  fdds = new DDS(dds);
604  eval->parse_constraint(constraint, *fdds);
605 
606  if (eval->function_clauses()) {
607  DDS *temp_fdds = eval->eval_function_clauses(*fdds);
608  delete fdds;
609  fdds = temp_fdds;
610  }
611 
612  ofstream data_stream(cache_file_name.c_str());
613  if (!data_stream)
614  throw InternalErr(__FILE__, __LINE__, "Could not open '" + cache_file_name + "' to write cached response.");
615 
616  string start="dataddx_cache_start", boundary="dataddx_cache_boundary";
617 
618  // Use a ConstraintEvaluator that has not parsed a CE so the code can use
619  // the send method(s)
620  ConstraintEvaluator eval;
621 
622  // Setting the version to 3.2 causes send_data_ddx to write the MIME headers that
623  // the cache expects.
624  fdds->set_dap_version("3.2");
625 
626  // This is a bit of a hack, but it effectively uses ResponseBuilder to write the
627  // cached object/response without calling the machinery in one of the send_*()
628  // methods. Those methods assume they need to evaluate the BESDapResponseBuilder's
629  // CE, which is not necessary and will alter the values of the send_p property
630  // of the DDS's variables.
631  set_mime_multipart(data_stream, boundary, start, dods_data_ddx, x_plain, last_modified_time(rb->get_dataset_name()));
632  //data_stream << flush;
633  rb->serialize_dap2_data_ddx(data_stream, *fdds, eval, boundary, start);
634  //data_stream << flush;
635 
636  data_stream << CRLF << "--" << boundary << "--" << CRLF;
637 
638  data_stream.close();
639 
640  // Change the exclusive lock on the new file to a shared lock. This keeps
641  // other processes from purging the new file and ensures that the reading
642  // process can use it.
643  exclusive_to_shared_lock(fd);
644 
645  // Now update the total cache size info and purge if needed. The new file's
646  // name is passed into the purge method because this process cannot detect its
647  // own lock on the file.
648  unsigned long long size = update_cache_info(cache_file_name);
649  if (cache_too_big(size))
650  update_and_purge(cache_file_name);
651  }
652  // get_read_lock() returns immediately if the file does not exist,
653  // but blocks waiting to get a shared lock if the file does exist.
654  else if (get_read_lock(cache_file_name, fd)) {
655  BESDEBUG("cache", "BESStoredDapResultCache::store_dap2_result() - function ce - cached hit: " << cache_file_name << endl);
656  fdds = get_cached_dap2_data_ddx(cache_file_name, &factory, dds.get_dataset_name());
657  }
658  else {
659  throw InternalErr(__FILE__, __LINE__, "BESStoredDapResultCache::store_dap2_result() - Cache error during function invocation.");
660  }
661 
662 
666  delete fdds;
667 
671  BESDEBUG("cache", "BESStoredDapResultCache::cache_dap2_dataset() - unlocking and closing cache file "<< cache_file_name << endl );
672  unlock_and_close(cache_file_name);
673 
674  }
675  catch (...) {
676  BESDEBUG("cache", "BESStoredDapResultCache::cache_dap2_dataset() - caught exception, unlocking cache and re-throw." << endl );
677  // I think this call is not needed. jhrg 10/23/12
678  unlock_cache();
679  throw;
680  }
681 
682 
683  BESDEBUG("cache", "BESStoredDapResultCache::store_dap2_result() - END (local_id=`"<< local_id << "'" << endl );
684  return local_id;
685 }
686 #endif
687 
692 string BESStoredDapResultCache::store_dap2_result(DDS &dds, const string &constraint, BESDapResponseBuilder *rb, ConstraintEvaluator *eval)
693 {
694  BESDEBUG("cache", "BESStoredDapResultCache::store_dap2_result() - BEGIN" << endl );
695  // These are used for the cached or newly created DDS object
696  BaseTypeFactory factory;
697 
698  // Get the cache filename for this thing. Do not use the default
699  // name mangling; instead use what build_cache_file_name() does.
700  string local_id = get_stored_result_local_id(dds.filename(), constraint, DAP_3_2);
701  BESDEBUG("cache", "BESStoredDapResultCache::store_dap2_result() - local_id: "<< local_id << endl );
702  string cache_file_name = get_cache_file_name(local_id, /*mangle*/false);
703  BESDEBUG("cache", "BESStoredDapResultCache::store_dap2_result() - cache_file_name: "<< cache_file_name << endl );
704  int fd;
705  try {
706  // If the object in the cache is not valid, remove it. The read_lock will
707  // then fail and the code will drop down to the create_and_lock() call.
708  // is_valid() tests for a non-zero object and for d_dateset newer than
709  // the cached object.
710  if (!is_valid(cache_file_name, dds.filename()))
711  purge_file(cache_file_name);
712 
713  if (get_read_lock(cache_file_name, fd)) {
714  BESDEBUG("cache", "BESStoredDapResultCache::store_dap2_result() - Stored Result already exists. Not rewriting file: " << cache_file_name << endl);
715  }
716  else if (create_and_lock(cache_file_name, fd)) {
717  // If here, the cache_file_name could not be locked for read access;
718  // try to build it. First make an empty file and get an exclusive lock on it.
719  BESDEBUG("cache", "BESStoredDapResultCache::store_dap2_result() - cache_file_name " << cache_file_name << ", constraint: " << constraint << endl);
720  DDS *fdds;
721 
722  fdds = new DDS(dds);
723  eval->parse_constraint(constraint, *fdds);
724 
725  if (eval->function_clauses()) {
726  DDS *temp_fdds = eval->eval_function_clauses(*fdds);
727  delete fdds;
728  fdds = temp_fdds;
729  }
730 
731  ofstream data_stream(cache_file_name.c_str());
732  if (!data_stream)
733  throw InternalErr(__FILE__, __LINE__, "Could not open '" + cache_file_name + "' to write cached response.");
734 
735  string start="dataddx_cache_start", boundary="dataddx_cache_boundary";
736 
737  // Use a ConstraintEvaluator that has not parsed a CE so the code can use
738  // the send method(s)
739  ConstraintEvaluator eval;
740 
741  // Setting the version to 3.2 causes send_data_ddx to write the MIME headers that
742  // the cache expects.
743  fdds->set_dap_version("3.2");
744 
745  // This is a bit of a hack, but it effectively uses ResponseBuilder to write the
746  // cached object/response without calling the machinery in one of the send_*()
747  // methods. Those methods assume they need to evaluate the BESDapResponseBuilder's
748  // CE, which is not necessary and will alter the values of the send_p property
749  // of the DDS's variables.
750  set_mime_multipart(data_stream, boundary, start, dods_data_ddx, x_plain, last_modified_time(rb->get_dataset_name()));
751  //data_stream << flush;
752  rb->serialize_dap2_data_ddx(data_stream, *fdds, eval, boundary, start);
753  //data_stream << flush;
754 
755  data_stream << CRLF << "--" << boundary << "--" << CRLF;
756 
757  data_stream.close();
758 
759  // Change the exclusive lock on the new file to a shared lock. This keeps
760  // other processes from purging the new file and ensures that the reading
761  // process can use it.
762  exclusive_to_shared_lock(fd);
763 
764  // Now update the total cache size info and purge if needed. The new file's
765  // name is passed into the purge method because this process cannot detect its
766  // own lock on the file.
767  unsigned long long size = update_cache_info(cache_file_name);
768  if (cache_too_big(size))
769  update_and_purge(cache_file_name);
773  delete fdds;
774  }
775  // get_read_lock() returns immediately if the file does not exist,
776  // but blocks waiting to get a shared lock if the file does exist.
777  else if (get_read_lock(cache_file_name, fd)) {
778  BESDEBUG("cache", "BESStoredDapResultCache::store_dap2_result() - Stored Result already exists. Not rewriting file: " << cache_file_name << endl);
779  }
780  else {
781  throw InternalErr(__FILE__, __LINE__, "BESStoredDapResultCache::store_dap2_result() - Cache error during function invocation.");
782  }
783 
784  BESDEBUG("cache", "BESStoredDapResultCache::store_dap2_result() - unlocking and closing cache file "<< cache_file_name << endl );
785  unlock_and_close(cache_file_name);
786  }
787  catch (...) {
788  BESDEBUG("cache", "BESStoredDapResultCache::store_dap2_result() - caught exception, unlocking cache and re-throw." << endl );
789  // I think this call is not needed. jhrg 10/23/12
790  unlock_cache();
791  throw;
792  }
793 
794  BESDEBUG("cache", "BESStoredDapResultCache::store_dap2_result() - END (local_id=`"<< local_id << "')" << endl );
795  return local_id;
796 }
797 
805 string
806 BESStoredDapResultCache::get_stored_result_local_id(const string &dataset, const string &ce, libdap::DAPVersion version )
807 {
808  BESDEBUG("cache", "get_stored_result_local_id() - BEGIN. dataset: " << dataset << ", ce: " << ce << endl);
809  std::ostringstream ostr;
810  HASH_OBJ<std::string> str_hash;
811  string name = dataset + "#" + ce;
812  ostr << str_hash(name);
813  string hashed_name = ostr.str();
814  BESDEBUG("cache", "get_stored_result_local_id() - hashed_name: " << hashed_name << endl);
815 
816  string suffix="";
817  switch(version){
818  case DAP_2_0:
819  suffix = ".dods";
820  break;
821 
822  case DAP_3_2:
823  suffix = ".data_ddx";
824  break;
825 
826  case DAP_4_0:
827  suffix = ".dap";
828  break;
829  default:
830  throw BESInternalError("BESStoredDapResultCache::get_stored_result_local_id() - Unrecognized DAP version!!", __FILE__, __LINE__);
831  break;
832  }
833 
834  BESDEBUG("cache", "get_stored_result_local_id() - Data file suffix: " << suffix << endl);
835 
836 
837  string local_id = d_resultFilePrefix + hashed_name + suffix;
838  BESDEBUG("cache", "get_stored_result_local_id() - file: " << local_id << endl);
839 
840  local_id = assemblePath(d_storedResultsSubdir,local_id);
841 
842  BESDEBUG("cache", "get_stored_result_local_id() - END. local_id: " << local_id << endl);
843  return local_id;
844 }
845 
846 
847 
848 
861 string BESStoredDapResultCache::get_cache_file_name(const string &local_id, bool mangle)
862 {
863  if(local_id.empty()){
864  throw BESInternalError("BESStoredDapResultCache: The target cache file name must not be an empty string. Srsly.", __FILE__, __LINE__);
865  }
866 
867  string cacheFile = assemblePath(d_dataRootDir,local_id);
868 
869  BESDEBUG("cache", "BESStoredDapResultCache::get_cache_file_name() - local_id: '" << local_id << "'" << endl);
870  BESDEBUG("cache", "BESStoredDapResultCache::get_cache_file_name() - cacheDir: '" << cacheFile << "'" << endl);
871 
872  if(mangle){
873  BESDEBUG("cache", "[WARNING] BESStoredDapResultCache::get_cache_file_name() - The parameter 'mangle' is ignored!" << endl);
874  }
875 
876 
877  return cacheFile;
878 }
879 
885 string BESStoredDapResultCache::store_dap4_result(DMR &dmr, const string &constraint, BESDapResponseBuilder *rb){
886  BESDEBUG("cache", "BESStoredDapResultCache::store_dap4_result() - BEGIN" << endl );
887  // These are used for the cached or newly created DDS object
888  BaseTypeFactory factory;
889 
890  // Get the cache filename for this thing. Do not use the default
891  // name mangling; instead use what build_cache_file_name() does.
892  string local_id = get_stored_result_local_id(dmr.filename(), constraint, DAP_4_0);
893  BESDEBUG("cache", "BESStoredDapResultCache::store_dap4_result() - local_id: "<< local_id << endl );
894  string cache_file_name = get_cache_file_name(local_id, /*mangle*/false);
895  BESDEBUG("cache", "BESStoredDapResultCache::store_dap4_result() - cache_file_name: "<< cache_file_name << endl );
896  int fd;
897  try {
898  // If the object in the cache is not valid, remove it. The read_lock will
899  // then fail and the code will drop down to the create_and_lock() call.
900  // is_valid() tests for a non-zero object and for d_dateset newer than
901  // the cached object.
902  if (!is_valid(cache_file_name, dmr.filename())){
903  BESDEBUG("cache", "BESStoredDapResultCache::store_dap4_result() - File is not valid. Purging file from cache. filename: " << cache_file_name << endl);
904  purge_file(cache_file_name);
905  }
906 
907  if (get_read_lock(cache_file_name, fd)) {
908  BESDEBUG("cache", "BESStoredDapResultCache::store_dap4_result() - Stored Result already exists. Not rewriting file: " << cache_file_name << endl);
909  }
910  else if (create_and_lock(cache_file_name, fd)) {
911  // If here, the cache_file_name could not be locked for read access;
912  // try to build it. First make an empty file and get an exclusive lock on it.
913  BESDEBUG("cache", "BESStoredDapResultCache::store_dap4_result() - cache_file_name: " << cache_file_name << ", constraint: " << constraint << endl);
914 
915  ofstream data_stream(cache_file_name.c_str());
916  if (!data_stream)
917  throw InternalErr(__FILE__, __LINE__, "Could not open '" + cache_file_name + "' to write cached response.");
918 
919  //data_stream << flush;
920  rb->serialize_dap4_data(data_stream, dmr, false);
921  //data_stream << flush;
922 
923  data_stream.close();
924 
925  // Change the exclusive lock on the new file to a shared lock. This keeps
926  // other processes from purging the new file and ensures that the reading
927  // process can use it.
928  exclusive_to_shared_lock(fd);
929 
930  // Now update the total cache size info and purge if needed. The new file's
931  // name is passed into the purge method because this process cannot detect its
932  // own lock on the file.
933  unsigned long long size = update_cache_info(cache_file_name);
934  if (cache_too_big(size))
935  update_and_purge(cache_file_name);
936  }
937  // get_read_lock() returns immediately if the file does not exist,
938  // but blocks waiting to get a shared lock if the file does exist.
939  else if (get_read_lock(cache_file_name, fd)) {
940  BESDEBUG("cache", "BESStoredDapResultCache::store_dap4_result() - Couldn't create and lock file, But I got a read lock. "
941  "Result may have been created by another process. "
942  "Not rewriting file: " << cache_file_name << endl);
943  }
944  else {
945  throw InternalErr(__FILE__, __LINE__, "BESStoredDapResultCache::store_dap4_result() - Cache error during function invocation.");
946  }
947 
948 
949 
953  BESDEBUG("cache", "BESStoredDapResultCache::store_dap4_result() - unlocking and closing cache file "<< cache_file_name << endl );
954  unlock_and_close(cache_file_name);
955 
956  }
957  catch (...) {
958  BESDEBUG("cache", "BESStoredDapResultCache::store_dap4_result() - caught exception, unlocking cache and re-throw." << endl );
959  // I think this call is not needed. jhrg 10/23/12
960  unlock_cache();
961  throw;
962  }
963 
964 
965  BESDEBUG("cache", "BESStoredDapResultCache::store_dap4_result() - END (local_id=`"<< local_id << "')" << endl );
966  return local_id;
967 
968 }
969 
#define BES_DATA_ROOT
exception thrown if inernal error encountered
#define BES_CATALOG_ROOT
static string lowercase(const string &s)
Convert a string to all lower case.
Definition: BESUtil.cc:182
static string assemblePath(const string &firstPart, const string &secondPart, bool addLeadingSlash=false)
virtual string get_cache_file_name(const string &src, bool mangle=false)
Build the name of file that will holds the uncompressed data from 'src' in the cache.
STL namespace.
virtual string store_dap4_result(libdap::DMR &dmr, const string &constraint, BESDapResponseBuilder *rb)
libdap::DMR * get_cached_dap4_data(const string &cache_file_name, libdap::D4BaseTypeFactory *factory, const string &filename)
Read data from cache.
static class NCMLUtil overview
virtual string get_message()
get the error message for this exception
Definition: BESError.h:94
#define CRLF
static BESStoredDapResultCache * get_instance()
Get the default instance of the BESStoredDapResultCache object.
libdap::DDS * get_cached_dap2_data_ddx(const std::string &cache_file_name, libdap::BaseTypeFactory *factory, const std::string &dataset)
Read data from cache.
static const string SUBDIR_KEY
static bool IsSet(const string &flagName)
see if the debug context flagName is set to true
Definition: BESDebug.h:160
static const string PREFIX_KEY
void get_value(const string &s, string &val, bool &found)
Retrieve the value of a given key, if set.
Definition: BESKeys.cc:453
virtual void serialize_dap2_data_ddx(std::ostream &out, libdap::DDS &dds, libdap::ConstraintEvaluator &eval, const std::string &boundary, const std::string &start, bool ce_eval=true)
Serialize a DAP3.2 DataDDX to the stream "out".
This class is used to build responses for/by the BES.
#define BESDEBUG(x, y)
macro used to send debug information to the debug stream
Definition: BESDebug.h:64
static BESKeys * TheKeys()
Definition: TheBESKeys.cc:48
virtual std::string get_dataset_name() const
The ``dataset name'' is the filename or other string that the filter program will use to access the d...
This class is used to cache DAP2 response objects.
virtual string store_dap2_result(libdap::DDS &dds, const std::string &constraint, BESDapResponseBuilder *rb, libdap::ConstraintEvaluator *eval)
virtual void serialize_dap4_data(std::ostream &out, libdap::DMR &dmr, bool with_mime_headers=true)
Serialize the DAP4 data response to the passed stream.