View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2015-2016 ForgeRock AS.
15   */
16  package org.forgerock.audit.handlers.csv;
17  
18  import static org.forgerock.util.Reject.checkNotNull;
19  
20  import java.io.BufferedReader;
21  import java.io.File;
22  import java.io.FileOutputStream;
23  import java.io.FileReader;
24  import java.io.IOException;
25  import java.io.Writer;
26  import java.util.Map;
27  
28  import org.forgerock.audit.events.handlers.writers.AsynchronousTextWriter;
29  import org.forgerock.audit.events.handlers.writers.RotatableWriter;
30  import org.forgerock.audit.events.handlers.writers.TextWriter;
31  import org.forgerock.audit.events.handlers.writers.TextWriterAdapter;
32  import org.forgerock.audit.handlers.csv.CsvAuditEventHandlerConfiguration.EventBufferingConfiguration;
33  import org.forgerock.util.Reject;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  import org.supercsv.io.CsvMapReader;
37  import org.supercsv.io.ICsvMapReader;
38  import org.supercsv.prefs.CsvPreference;
39  
40  /**
41   * Responsible for writing to a CSV file.
42   */
43  class StandardCsvWriter implements CsvWriter {
44  
45      private static final Logger logger = LoggerFactory.getLogger(StandardCsvWriter.class);
46  
47      private final CsvFormatter csvFormatter;
48      private final String[] headers;
49      private final Writer csvWriter;
50      private RotatableWriter rotatableWriter;
51  
52      StandardCsvWriter(File csvFile, String[] headers, CsvPreference csvPreference,
53              CsvAuditEventHandlerConfiguration config) throws IOException {
54          Reject.ifTrue(config.getSecurity().isEnabled(), "StandardCsvWriter should not be used if security is enabled");
55          boolean fileAlreadyInitialized = csvFile.exists();
56          if (fileAlreadyInitialized) {
57              try (ICsvMapReader reader = new CsvMapReader(new BufferedReader(new FileReader(csvFile)), csvPreference)) {
58                  final String[] actualHeaders = reader.getHeader(true);
59                  // Assert that the 2 headers equals.
60                  if (actualHeaders == null) {
61                      fileAlreadyInitialized = false;
62                  } else {
63                      if (actualHeaders.length != headers.length) {
64                          throw new IOException("Resuming an existing CSV file but the headers do not match.");
65                      }
66                      for (int idx = 0; idx < actualHeaders.length; idx++) {
67                          if (!actualHeaders[idx].equals(headers[idx])) {
68                              throw new IOException("Resuming an existing CSV file but the headers do not match.");
69                          }
70                      }
71                  }
72              }
73          }
74          this.headers = checkNotNull(headers, "The headers can't be null.");
75          csvFormatter = new CsvFormatter(csvPreference);
76          csvWriter = constructWriter(csvFile, fileAlreadyInitialized, config);
77  
78          if (rotatableWriter != null) {
79              rotatableWriter.registerRotationHooks(new CsvRotationHooks(csvFormatter, headers));
80          }
81  
82          if (!fileAlreadyInitialized) {
83              writeHeader(headers);
84              csvWriter.flush();
85          }
86      }
87  
88      private Writer constructWriter(File csvFile, boolean append, CsvAuditEventHandlerConfiguration config)
89              throws IOException {
90          TextWriter textWriter;
91          if (config.getFileRotation().isRotationEnabled()) {
92              rotatableWriter = new RotatableWriter(csvFile, config, append);
93              textWriter = rotatableWriter;
94          } else {
95              textWriter = new TextWriter.Stream(new FileOutputStream(csvFile, append));
96          }
97  
98          if (config.getBuffering().isEnabled()) {
99              EventBufferingConfiguration bufferConfig = config.getBuffering();
100             textWriter = new AsynchronousTextWriter("CsvHandler", bufferConfig.isAutoFlush(), textWriter);
101         }
102         return new TextWriterAdapter(textWriter);
103     }
104 
105     /**
106      * Forces rotation of the writer.
107      * <p>
108      * Rotation is possible only if file rotation is enabled.
109      *
110      * @return {@code true} if rotation was done, {@code false} otherwise.
111      * @throws IOException
112      *          If an error occurs
113      */
114     @Override
115     public boolean forceRotation() throws IOException {
116         return rotatableWriter != null ? rotatableWriter.forceRotation() : false;
117     }
118 
119     public void writeHeader(String... headers) throws IOException {
120         csvWriter.write(csvFormatter.formatHeader(headers));
121     }
122 
123     /**
124      * Write a row into the CSV files.
125      * @param values The keys of the {@link Map} have to match the column's header.
126      * @throws IOException
127      */
128     @Override
129     public void writeEvent(Map<String, String> values) throws IOException {
130         csvWriter.write(csvFormatter.formatEvent(values, headers));
131     }
132 
133     /**
134      * Flush the data into the CSV file.
135      * @throws IOException
136      */
137     public void flush() throws IOException {
138         csvWriter.flush();
139     }
140 
141     @Override
142     public void close() throws IOException {
143         csvWriter.close();
144     }
145 
146 }