METAMODEL-1165: Fixed - added default_table alias table
[metamodel.git] / csv / src / main / java / org / apache / metamodel / csv / CsvUpdateCallback.java
1 /**
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.metamodel.csv;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.OutputStream;
24 import java.io.RandomAccessFile;
25 import java.io.Writer;
26 import java.nio.ByteBuffer;
27 import java.nio.channels.FileChannel;
28 import java.nio.charset.Charset;
29
30 import org.apache.metamodel.AbstractUpdateCallback;
31 import org.apache.metamodel.MetaModelException;
32 import org.apache.metamodel.MetaModelHelper;
33 import org.apache.metamodel.UpdateCallback;
34 import org.apache.metamodel.create.TableCreationBuilder;
35 import org.apache.metamodel.delete.RowDeletionBuilder;
36 import org.apache.metamodel.drop.TableDropBuilder;
37 import org.apache.metamodel.insert.RowInsertionBuilder;
38 import org.apache.metamodel.schema.Schema;
39 import org.apache.metamodel.schema.Table;
40 import org.apache.metamodel.update.RowUpdationBuilder;
41 import org.apache.metamodel.util.Action;
42 import org.apache.metamodel.util.EqualsBuilder;
43 import org.apache.metamodel.util.FileHelper;
44 import org.apache.metamodel.util.FileResource;
45 import org.apache.metamodel.util.Resource;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 final class CsvUpdateCallback extends AbstractUpdateCallback implements UpdateCallback {
50
51 private static final Logger logger = LoggerFactory.getLogger(CsvUpdateCallback.class);
52
53 private final CsvConfiguration _configuration;
54 private final Resource _resource;
55 private Writer _writer;
56
57 public CsvUpdateCallback(CsvDataContext dataContext) {
58 super(dataContext);
59 _resource = dataContext.getResource();
60 _configuration = dataContext.getConfiguration();
61 }
62
63 @Override
64 public TableCreationBuilder createTable(Schema schema, String name) throws IllegalArgumentException,
65 IllegalStateException {
66 return new CsvCreateTableBuilder(this, MetaModelHelper.resolveUnderlyingSchema(schema), name);
67 }
68
69 @Override
70 public RowInsertionBuilder insertInto(Table table) throws IllegalArgumentException, IllegalStateException {
71 validateTable(table);
72 return new CsvInsertBuilder(this, table);
73 }
74
75 public CsvConfiguration getConfiguration() {
76 return _configuration;
77 }
78
79 public Resource getResource() {
80 return _resource;
81 }
82
83 private void validateTable(Table table) {
84 if (!(table instanceof CsvTable)) {
85 throw new IllegalArgumentException("Not a valid CSV table: " + table);
86 }
87 }
88
89 protected synchronized void writeRow(final String[] stringValues, final boolean append) {
90 final CsvWriter csvWriter = new CsvWriter(_configuration);
91 final String line = csvWriter.buildLine(stringValues);
92 final Writer writer = getWriter(append);
93 try {
94 writer.write(line);
95 } catch (IOException e) {
96 throw new MetaModelException("Failed to write line: " + line, e);
97 }
98 }
99
100 private Writer getWriter(boolean append) {
101 if (_writer == null || !append) {
102 final boolean needsLineBreak = needsLineBreak(_resource, _configuration);
103
104 final OutputStream out;
105 if (append) {
106 out = _resource.append();
107 } else {
108 out = _resource.write();
109 }
110
111 final boolean insertBom = !append;
112
113 final Writer writer = FileHelper.getWriter(out, _configuration.getEncoding(), insertBom);
114
115 if (needsLineBreak) {
116 try {
117 writer.write('\n');
118 } catch (IOException e) {
119 logger.debug("Failed to insert newline", e);
120 }
121 }
122 _writer = writer;
123 }
124 return _writer;
125 }
126
127 protected static boolean needsLineBreak(Resource resource, CsvConfiguration configuration) {
128 if (!resource.isExists() || resource.getSize() == 0) {
129 return false;
130 }
131
132 if (resource instanceof FileResource) {
133
134 final File file = ((FileResource) resource).getFile();
135
136 try {
137 // find the bytes a newline would match under the encoding
138 final byte[] bytesInLineBreak;
139 {
140 ByteBuffer encodedLineBreak = Charset.forName(configuration.getEncoding()).encode("\n");
141 bytesInLineBreak = new byte[encodedLineBreak.capacity()];
142 encodedLineBreak.get(bytesInLineBreak);
143 }
144
145 // find the last bytes of the file
146 final byte[] bytesFromFile = new byte[bytesInLineBreak.length];
147 {
148 final RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
149 try {
150 FileChannel channel = randomAccessFile.getChannel();
151 try {
152 long length = randomAccessFile.length();
153
154 channel = channel.position(length - bytesInLineBreak.length);
155 channel.read(ByteBuffer.wrap(bytesFromFile));
156 } finally {
157 channel.close();
158 }
159 } finally {
160 randomAccessFile.close();
161 }
162 }
163
164 // if the two byte arrays match, then the newline is not needed.
165 if (EqualsBuilder.equals(bytesInLineBreak, bytesFromFile)) {
166 return false;
167 }
168 return true;
169 } catch (Exception e) {
170 logger.error("Error occurred while checking if file needs linebreak, omitting check", e);
171 }
172 }
173
174 return false;
175 }
176
177 /**
178 * Closes all open handles
179 */
180 protected void close() {
181 if (_writer != null) {
182 try {
183 _writer.flush();
184 } catch (IOException e) {
185 logger.warn("Failed to flush CSV writer", e);
186 }
187 try {
188 _writer.close();
189 } catch (IOException e) {
190 logger.error("Failed to close CSV writer", e);
191 } finally {
192 _writer = null;
193 }
194 }
195 }
196
197 @Override
198 public RowUpdationBuilder update(Table table) throws IllegalArgumentException, IllegalStateException {
199 close();
200 return super.update(table);
201 }
202
203 @Override
204 public boolean isDropTableSupported() {
205 return true;
206 }
207
208 @Override
209 public TableDropBuilder dropTable(Table table) {
210 validateTable(table);
211 return new CsvTableDropBuilder(this, table);
212 }
213
214 /**
215 * Callback method used by {@link CsvTableDropBuilder} when execute is
216 * called
217 */
218 protected void dropTable() {
219 close();
220 if (_resource instanceof FileResource) {
221 final File file = ((FileResource) _resource).getFile();
222 final boolean success = file.delete();
223 if (!success) {
224 throw new MetaModelException("Could not delete (drop) file: " + file);
225 }
226 } else {
227 _resource.write(new Action<OutputStream>() {
228 @Override
229 public void run(OutputStream arg) throws Exception {
230 // do nothing, just write an empty file
231 }
232 });
233 }
234 }
235
236 @Override
237 public boolean isDeleteSupported() {
238 return true;
239 }
240
241 @Override
242 public RowDeletionBuilder deleteFrom(Table table) {
243 validateTable(table);
244 return new CsvDeleteBuilder(this, table);
245 }
246 }