Kea: csv_file.cc Source File

| 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
87 CSVFile::~CSVFile() {
88 close();
89 }
90
91 void
92 CSVFile::close() {
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
102 CSVFile::exists() const {
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
333 CSVFile::recreate() {
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
364 CSVFile::validate(const CSVRow& row) {
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
378 CSVFile::validateHeader(const CSVRow& header) {
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
isc::util::CSVFile::flush
void flush() const
Flushes a file.
Definition: csv_file.cc:110
isc::util::CSVFile::open
virtual void open(const bool seek_to_end=false)
Opens existing file or creates a new one.
Definition: csv_file.cc:265
isc::util::CSVFile::addColumn
void addColumn(const std::string &col_name)
Adds new column name.
Definition: csv_file.cc:116
isc::util::CSVFile::getReadMsg
std::string getReadMsg() const
Returns the description of the last error returned by the CSVFile::next function.
Definition: csv_file.h:348
isc::util::CSVFile::exists
bool exists() const
Checks if the CSV file exists and can be opened for reading.
Definition: csv_file.cc:102
isc::util::CSVFile::validateHeader
virtual bool validateHeader(const CSVRow &header)
This function validates the header of the CSV file.
Definition: csv_file.cc:378
isc::util::CSVFile::setReadMsg
void setReadMsg(const std::string &read_msg)
Sets error message after row validation.
Definition: csv_file.h:418
isc::util::CSVFile::append
void append(const CSVRow &row) const
Writes the CSV row into the file.
Definition: csv_file.cc:136
isc::Exception
This is a base class for exceptions thrown from the DNS library module.
Definition: exceptions/exceptions.h:23
isc::util::CSVFile::getColumnCount
size_t getColumnCount() const
Returns the number of columns in the file.
Definition: csv_file.h:335
isc::util::CSVFile::validate
virtual bool validate(const CSVRow &row)
Validate the row read from a file.
Definition: csv_file.cc:364
isc
Defines the logger used by the top-level component of kea-dhcp-ddns.
Definition: agent_parser.cc:144
isc::Exception::what
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
Definition: exceptions/exceptions.cc:32
isc::util::CSVRow::writeAt
void writeAt(const size_t at, const char *value)
Replaces the value at specified index.
Definition: csv_file.cc:58
isc_throw
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
Definition: exceptions/exceptions.h:192
isc::util::CSVRow::readAt
std::string readAt(const size_t at) const
Retrieves a value from the internal container.
Definition: csv_file.cc:39
isc::util::CSVFile::recreate
virtual void recreate()
Creates a new CSV file.
Definition: csv_file.cc:333
csv_file.h
isc::util::CSVRow
Represents a single row of the CSV file.
Definition: csv_file.h:51
isc::util::CSVFile::getFilename
std::string getFilename() const
Returns the path to the CSV file.
Definition: csv_file.h:340
isc::util::CSVFile::next
bool next(CSVRow &row, const bool skip_validation=false)
Reads next row from CSV file.
Definition: csv_file.cc:226
isc::util::CSVRow::CSVRow
CSVRow(const size_t cols=0, const char separator=',')
Constructor, creates the raw to be used for output.
Definition: csv_file.cc:20
isc::util::CSVRow::render
std::string render() const
Creates a text representation of the CSV file row.
Definition: csv_file.cc:45
isc::util::operator<<
std::ostream & operator<<(std::ostream &os, const CSVRow &row)
Overrides standard output stream operator for CSVRow object.
Definition: csv_file.cc:69
isc::util::CSVFile::~CSVFile
virtual ~CSVFile()
Destructor.
Definition: csv_file.cc:87
isc::OutOfRange
A generic exception that is thrown if a parameter given to a method would refer to or modify out-of-r...
Definition: exceptions/exceptions.h:115
isc::util::CSVFile::getColumnName
std::string getColumnName(const size_t col_index) const
Returns the name of the column.
Definition: csv_file.cc:216
isc::util::CSVFileError
Exception thrown when an error occurs during CSV file processing.
Definition: csv_file.h:22
isc::util::CSVRow::getValuesCount
size_t getValuesCount() const
Returns number of values in a CSV row.
Definition: csv_file.h:85
isc::util::CSVRow::parse
void parse(const std::string &line)
Parse the CSV file row.
Definition: csv_file.cc:31
isc::util::CSVFile::addColumnInternal
void addColumnInternal(const std::string &col_name)
Adds a column regardless if the file is open or not.
Definition: csv_file.cc:127
isc::util::CSVFile::CSVFile
CSVFile(const std::string &filename)
Constructor.
Definition: csv_file.cc:83
isc::util::CSVFile::getColumnIndex
size_t getColumnIndex(const std::string &col_name) const
Returns the index of the column having specified name.
Definition: csv_file.cc:206
isc::util::CSVRow::trim
void trim(const size_t count)
Trims a given number of elements from the end of a row.
Definition: csv_file.cc:64
isc::util::CSVFile::EMPTY_ROW
static CSVRow EMPTY_ROW()
Represents empty row.
Definition: csv_file.h:423
isc::util::CSVFile::close
void close()
Closes the CSV file.
Definition: csv_file.cc:92