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
17namespace isc {
18namespace util {
19
20CSVRow::CSVRow(const size_t cols, const char separator)
21 : separator_(1, separator), values_(cols) {
22}
23
24CSVRow::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
30void
31CSVRow::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
38std::string
39CSVRow::readAt(const size_t at) const {
40 checkIndex(at);
41 return (values_[at]);
42}
43
44std::string
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
57void
58CSVRow::writeAt(const size_t at, const char* value) {
59 checkIndex(at);
60 values_[at] = value;
61}
62
63void
64CSVRow::trim(const size_t count) {
65 checkIndex(count);
66 values_.resize(values_.size() - count);
67}
68
69std::ostream& operator<<(std::ostream& os, const CSVRow& row) {
70 os << row.render();
71 return (os);
72}
73
74void
75CSVRow::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
83CSVFile::CSVFile(const std::string& filename)
84 : filename_(filename), fs_(), cols_(0), read_msg_() {
85}
86
88 close();
89}
90
91void
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
101bool
103 std::ifstream fs(filename_.c_str());
104 const bool file_exists = fs.good();
105 fs.close();
106 return (file_exists);
107}
108
109void
111 checkStreamStatusAndReset("flush");
112 fs_->flush();
113}
114
115void
116CSVFile::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
126void
127CSVFile::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
135void
136CSVFile::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
166void
167CSVFile::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
182std::streampos
183CSVFile::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
205size_t
206CSVFile::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
215std::string
216CSVFile::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
225bool
226CSVFile::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
264void
265CSVFile::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
332void
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();
359 }
360
361}
362
363bool
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
377bool
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
This is a base class for exceptions thrown from the DNS library module.
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
A generic exception that is thrown if a parameter given to a method would refer to or modify out-of-r...
Exception thrown when an error occurs during CSV file processing.
Definition: csv_file.h:22
std::string getColumnName(const size_t col_index) const
Returns the name of the column.
Definition: csv_file.cc:216
void close()
Closes the CSV file.
Definition: csv_file.cc:92
size_t getColumnCount() const
Returns the number of columns in the file.
Definition: csv_file.h:335
virtual ~CSVFile()
Destructor.
Definition: csv_file.cc:87
bool exists() const
Checks if the CSV file exists and can be opened for reading.
Definition: csv_file.cc:102
virtual bool validate(const CSVRow &row)
Validate the row read from a file.
Definition: csv_file.cc:364
static CSVRow EMPTY_ROW()
Represents empty row.
Definition: csv_file.h:423
void setReadMsg(const std::string &read_msg)
Sets error message after row validation.
Definition: csv_file.h:418
CSVFile(const std::string &filename)
Constructor.
Definition: csv_file.cc:83
std::string getFilename() const
Returns the path to the CSV file.
Definition: csv_file.h:340
void flush() const
Flushes a file.
Definition: csv_file.cc:110
virtual bool validateHeader(const CSVRow &header)
This function validates the header of the CSV file.
Definition: csv_file.cc:378
void addColumnInternal(const std::string &col_name)
Adds a column regardless if the file is open or not.
Definition: csv_file.cc:127
virtual void recreate()
Creates a new CSV file.
Definition: csv_file.cc:333
std::string getReadMsg() const
Returns the description of the last error returned by the CSVFile::next function.
Definition: csv_file.h:348
void append(const CSVRow &row) const
Writes the CSV row into the file.
Definition: csv_file.cc:136
void addColumn(const std::string &col_name)
Adds new column name.
Definition: csv_file.cc:116
size_t getColumnIndex(const std::string &col_name) const
Returns the index of the column having specified name.
Definition: csv_file.cc:206
virtual void open(const bool seek_to_end=false)
Opens existing file or creates a new one.
Definition: csv_file.cc:265
bool next(CSVRow &row, const bool skip_validation=false)
Reads next row from CSV file.
Definition: csv_file.cc:226
Represents a single row of the CSV file.
Definition: csv_file.h:51
std::string render() const
Creates a text representation of the CSV file row.
Definition: csv_file.cc:45
size_t getValuesCount() const
Returns number of values in a CSV row.
Definition: csv_file.h:85
void trim(const size_t count)
Trims a given number of elements from the end of a row.
Definition: csv_file.cc:64
CSVRow(const size_t cols=0, const char separator=',')
Constructor, creates the raw to be used for output.
Definition: csv_file.cc:20
void writeAt(const size_t at, const char *value)
Replaces the value at specified index.
Definition: csv_file.cc:58
std::string readAt(const size_t at) const
Retrieves a value from the internal container.
Definition: csv_file.cc:39
void parse(const std::string &line)
Parse the CSV file row.
Definition: csv_file.cc:31
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
std::ostream & operator<<(std::ostream &os, const CSVRow &row)
Overrides standard output stream operator for CSVRow object.
Definition: csv_file.cc:69
Defines the logger used by the top-level component of kea-dhcp-ddns.