Kea  1.5.0
csv_file.cc
Go to the documentation of this file.
1 // Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 
7 #include <config.h>
8 
9 #include <util/csv_file.h>
10 #include <boost/algorithm/string/classification.hpp>
11 #include <boost/algorithm/string/constants.hpp>
12 #include <boost/algorithm/string/split.hpp>
13 #include <algorithm>
14 #include <fstream>
15 #include <sstream>
16 
17 namespace isc {
18 namespace util {
19 
20 CSVRow::CSVRow(const size_t cols, const char separator)
21  : separator_(1, separator), values_(cols) {
22 }
23 
24 CSVRow::CSVRow(const std::string& text, const char separator)
25  : separator_(1, separator) {
26  // Parsing is exception safe, so this will not throw.
27  parse(text);
28 }
29 
30 void
31 CSVRow::parse(const std::string& line) {
32  // Tokenize the string using a specified separator. Disable compression,
33  // so as the two consecutive separators mark an empty value.
34  boost::split(values_, line, boost::is_any_of(separator_),
35  boost::algorithm::token_compress_off);
36 }
37 
38 std::string
39 CSVRow::readAt(const size_t at) const {
40  checkIndex(at);
41  return (values_[at]);
42 }
43 
44 std::string
45 CSVRow::render() const {
46  std::ostringstream s;
47  for (size_t i = 0; i < values_.size(); ++i) {
48  // Do not put separator before the first value.
49  if (i > 0) {
50  s << separator_;
51  }
52  s << values_[i];
53  }
54  return (s.str());
55 }
56 
57 void
58 CSVRow::writeAt(const size_t at, const char* value) {
59  checkIndex(at);
60  values_[at] = value;
61 }
62 
63 void
64 CSVRow::trim(const size_t count) {
65  checkIndex(count);
66  values_.resize(values_.size() - count);
67 }
68 
69 std::ostream& operator<<(std::ostream& os, const CSVRow& row) {
70  os << row.render();
71  return (os);
72 }
73 
74 void
75 CSVRow::checkIndex(const size_t at) const {
76  if (at >= values_.size()) {
77  isc_throw(CSVFileError, "value index '" << at << "' of the CSV row"
78  " is out of bounds; maximal index is '"
79  << (values_.size() - 1) << "'");
80  }
81 }
82 
83 CSVFile::CSVFile(const std::string& filename)
84  : filename_(filename), fs_(), cols_(0), read_msg_() {
85 }
86 
88  close();
89 }
90 
91 void
93  // It is allowed to close multiple times. If file has been already closed,
94  // this is no-op.
95  if (fs_) {
96  fs_->close();
97  fs_.reset();
98  }
99 }
100 
101 bool
103  std::ifstream fs(filename_.c_str());
104  const bool file_exists = fs.good();
105  fs.close();
106  return (file_exists);
107 }
108 
109 void
110 CSVFile::flush() const {
111  checkStreamStatusAndReset("flush");
112  fs_->flush();
113 }
114 
115 void
116 CSVFile::addColumn(const std::string& col_name) {
117  // It is not allowed to add a new column when file is open.
118  if (fs_) {
119  isc_throw(CSVFileError, "attempt to add a column '" << col_name
120  << "' while the file '" << getFilename()
121  << "' is open");
122  }
123  addColumnInternal(col_name);
124 }
125 
126 void
127 CSVFile::addColumnInternal(const std::string& col_name) {
128  if (std::find(cols_.begin(), cols_.end(), col_name) != cols_.end()) {
129  isc_throw(CSVFileError, "attempt to add duplicate column '"
130  << col_name << "'");
131  }
132  cols_.push_back(col_name);
133 }
134 
135 void
136 CSVFile::append(const CSVRow& row) const {
137  checkStreamStatusAndReset("append");
138 
139  if (row.getValuesCount() != getColumnCount()) {
140  isc_throw(CSVFileError, "number of values in the CSV row '"
141  << row.getValuesCount() << "' doesn't match the number of"
142  " columns in the CSV file '" << getColumnCount() << "'");
143  }
144 
153  fs_->seekp(0, std::ios_base::end);
154  fs_->seekg(0, std::ios_base::end);
155  fs_->clear();
156 
157  std::string text = row.render();
158  *fs_ << text << std::endl;
159  if (!fs_->good()) {
160  fs_->clear();
161  isc_throw(CSVFileError, "failed to write CSV row '"
162  << text << "' to the file '" << filename_ << "'");
163  }
164 }
165 
166 void
167 CSVFile::checkStreamStatusAndReset(const std::string& operation) const {
168  if (!fs_) {
169  isc_throw(CSVFileError, "NULL stream pointer when performing '"
170  << operation << "' on file '" << filename_ << "'");
171 
172  } else if (!fs_->is_open()) {
173  fs_->clear();
174  isc_throw(CSVFileError, "closed stream when performing '"
175  << operation << "' on file '" << filename_ << "'");
176 
177  } else {
178  fs_->clear();
179  }
180 }
181 
182 std::streampos
183 CSVFile::size() const {
184  std::ifstream fs(filename_.c_str());
185  bool ok = fs.good();
186  // If something goes wrong, including that the file doesn't exist,
187  // return 0.
188  if (!ok) {
189  fs.close();
190  return (0);
191  }
192  std::ifstream::pos_type pos;
193  try {
194  // Seek to the end of file and see where we are. This is a size of
195  // the file.
196  fs.seekg(0, std::ifstream::end);
197  pos = fs.tellg();
198  fs.close();
199  } catch (const std::exception&) {
200  return (0);
201  }
202  return (pos);
203 }
204 
205 size_t
206 CSVFile::getColumnIndex(const std::string& col_name) const {
207  for (size_t i = 0; i < cols_.size(); ++i) {
208  if (cols_[i] == col_name) {
209  return (i);
210  }
211  }
212  isc_throw(isc::OutOfRange, "column '" << col_name << "' doesn't exist");
213 }
214 
215 std::string
216 CSVFile::getColumnName(const size_t col_index) const {
217  if (col_index >= cols_.size()) {
218  isc_throw(isc::OutOfRange, "column index " << col_index << " in the "
219  " CSV file '" << filename_ << "' is out of range; the CSV"
220  " file has only " << cols_.size() << " columns ");
221  }
222  return (cols_[col_index]);
223 }
224 
225 bool
226 CSVFile::next(CSVRow& row, const bool skip_validation) {
227  // Set something as row validation error. Although, we haven't started
228  // actual row validation we should get rid of any previously recorded
229  // errors so as the caller doesn't interpret them as the current one.
230  setReadMsg("validation not started");
231 
232  try {
233  // Check that stream is "ready" for any IO operations.
234  checkStreamStatusAndReset("get next row");
235 
236  } catch (isc::Exception& ex) {
237  setReadMsg(ex.what());
238  return (false);
239  }
240 
241  // Get exactly one line of the file.
242  std::string line;
243  std::getline(*fs_, line);
244  // If we got empty line because we reached the end of file
245  // return an empty row.
246  if (line.empty() && fs_->eof()) {
247  row = EMPTY_ROW();
248  return (true);
249 
250  } else if (!fs_->good()) {
251  // If we hit an IO error, communicate it to the caller but do NOT close
252  // the stream. Caller may try again.
253  setReadMsg("error reading a row from CSV file '"
254  + std::string(filename_) + "'");
255  return (false);
256  }
257  // If we read anything, parse it.
258  row.parse(line);
259 
260  // And check if it is correct.
261  return (skip_validation ? true : validate(row));
262 }
263 
264 void
265 CSVFile::open(const bool seek_to_end) {
266  // If file doesn't exist or is empty, we have to create our own file.
267  if (size() == static_cast<std::streampos>(0)) {
268  recreate();
269 
270  } else {
271  // Try to open existing file, holding some data.
272  fs_.reset(new std::fstream(filename_.c_str()));
273 
274  // Catch exceptions so as we can close the file if error occurs.
275  try {
276  // The file may fail to open. For example, because of insufficient
277  // permissions. Although the file is not open we should call close
278  // to reset our internal pointer.
279  if (!fs_->is_open()) {
280  isc_throw(CSVFileError, "unable to open '" << filename_ << "'");
281  }
282  // Make sure we are on the beginning of the file, so as we
283  // can parse the header.
284  fs_->seekg(0);
285  if (!fs_->good()) {
286  isc_throw(CSVFileError, "unable to set read pointer in the file '"
287  << filename_ << "'");
288  }
289 
290  // Read the header.
291  CSVRow header;
292  if (!next(header, true)) {
293  isc_throw(CSVFileError, "failed to read and parse header of the"
294  " CSV file '" << filename_ << "': "
295  << getReadMsg());
296  }
297 
298  // Check the header against the columns specified for the CSV file.
299  if (!validateHeader(header)) {
300  isc_throw(CSVFileError, "invalid header '" << header
301  << "' in CSV file '" << filename_ << "': "
302  << getReadMsg());
303  }
304 
305  // Everything is good, so if we haven't added any columns yet,
306  // add them.
307  if (getColumnCount() == 0) {
308  for (size_t i = 0; i < header.getValuesCount(); ++i) {
309  addColumnInternal(header.readAt(i));
310  }
311  }
312 
313  // If caller requested that the pointer is set at the end of file,
314  // move both read and write pointer.
315  if (seek_to_end) {
316  fs_->seekp(0, std::ios_base::end);
317  fs_->seekg(0, std::ios_base::end);
318  if (!fs_->good()) {
319  isc_throw(CSVFileError, "unable to move to the end of"
320  " CSV file '" << filename_ << "'");
321  }
322  fs_->clear();
323  }
324 
325  } catch (const std::exception&) {
326  close();
327  throw;
328  }
329  }
330 }
331 
332 void
334  // There is no sense creating a file if we don't specify columns for it.
335  if (getColumnCount() == 0) {
336  close();
337  isc_throw(CSVFileError, "no columns defined for the newly"
338  " created CSV file '" << filename_ << "'");
339  }
340 
341  // Close any dangling files.
342  close();
343  fs_.reset(new std::fstream(filename_.c_str(), std::fstream::out));
344  if (!fs_->is_open()) {
345  close();
346  isc_throw(CSVFileError, "unable to open '" << filename_ << "'");
347  }
348  // Opened successfully. Write a header to it.
349  try {
350  CSVRow header(getColumnCount());
351  for (size_t i = 0; i < getColumnCount(); ++i) {
352  header.writeAt(i, getColumnName(i));
353  }
354  *fs_ << header << std::endl;
355 
356  } catch (const std::exception& ex) {
357  close();
358  isc_throw(CSVFileError, ex.what());
359  }
360 
361 }
362 
363 bool
365  setReadMsg("success");
366  bool ok = (row.getValuesCount() == getColumnCount());
367  if (!ok) {
368  std::ostringstream s;
369  s << "the size of the row '" << row << "' doesn't match the number of"
370  " columns '" << getColumnCount() << "' of the CSV file '"
371  << filename_ << "'";
372  setReadMsg(s.str());
373  }
374  return (ok);
375 }
376 
377 bool
379  if (getColumnCount() == 0) {
380  return (true);
381  }
382 
383  if (getColumnCount() != header.getValuesCount()) {
384  return (false);
385  }
386 
387  for (size_t i = 0; i < getColumnCount(); ++i) {
388  if (getColumnName(i) != header.readAt(i)) {
389  return (false);
390  }
391  }
392  return (true);
393 }
394 
395 } // end of isc::util namespace
396 } // end of isc namespace
bool exists() const
Checks if the CSV file exists and can be opened for reading.
Definition: csv_file.cc:102
virtual bool validateHeader(const CSVRow &header)
This function validates the header of the CSV file.
Definition: csv_file.cc:378
virtual void recreate()
Creates a new CSV file.
Definition: csv_file.cc:333
size_t getColumnCount() const
Returns the number of columns in the file.
Definition: csv_file.h:335
static CSVRow EMPTY_ROW()
Represents empty row.
Definition: csv_file.h:423
virtual ~CSVFile()
Destructor.
Definition: csv_file.cc:87
CSVFile(const std::string &filename)
Constructor.
Definition: csv_file.cc:83
void append(const CSVRow &row) const
Writes the CSV row into the file.
Definition: csv_file.cc:136
std::ostream & operator<<(std::ostream &os, const CSVRow &row)
Overrides standard output stream operator for CSVRow object.
Definition: csv_file.cc:69
std::string getFilename() const
Returns the path to the CSV file.
Definition: csv_file.h:340
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
void trim(const size_t count)
Trims a given number of elements from the end of a row.
Definition: csv_file.cc:64
virtual bool validate(const CSVRow &row)
Validate the row read from a file.
Definition: csv_file.cc:364
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
void addColumnInternal(const std::string &col_name)
Adds a column regardless if the file is open or not.
Definition: csv_file.cc:127
size_t getValuesCount() const
Returns number of values in a CSV row.
Definition: csv_file.h:85
void parse(const std::string &line)
Parse the CSV file row.
Definition: csv_file.cc:31
void close()
Closes the CSV file.
Definition: csv_file.cc:92
Represents a single row of the CSV file.
Definition: csv_file.h:51
void setReadMsg(const std::string &read_msg)
Sets error message after row validation.
Definition: csv_file.h:418
This is a base class for exceptions thrown from the DNS library module.
Defines the logger used by the top-level component of kea-dhcp-ddns.
CSVRow(const size_t cols=0, const char separator=',')
Constructor, creates the raw to be used for output.
Definition: csv_file.cc:20
void addColumn(const std::string &col_name)
Adds new column name.
Definition: csv_file.cc:116
void flush() const
Flushes a file.
Definition: csv_file.cc:110
std::string getColumnName(const size_t col_index) const
Returns the name of the column.
Definition: csv_file.cc:216
std::string getReadMsg() const
Returns the description of the last error returned by the CSVFile::next function.
Definition: csv_file.h:348
size_t getColumnIndex(const std::string &col_name) const
Returns the index of the column having specified name.
Definition: csv_file.cc:206
std::string readAt(const size_t at) const
Retrieves a value from the internal container.
Definition: csv_file.cc:39
A generic exception that is thrown if a parameter given to a method would refer to or modify out-of-r...
void writeAt(const size_t at, const char *value)
Replaces the value at specified index.
Definition: csv_file.cc:58
bool next(CSVRow &row, const bool skip_validation=false)
Reads next row from CSV file.
Definition: csv_file.cc:226
std::string render() const
Creates a text representation of the CSV file row.
Definition: csv_file.cc:45
virtual void open(const bool seek_to_end=false)
Opens existing file or creates a new one.
Definition: csv_file.cc:265
Exception thrown when an error occurs during CSV file processing.
Definition: csv_file.h:22