1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.healthmarketscience.jackcess.impl.complex;
18
19 import java.io.ByteArrayInputStream;
20 import java.io.DataInputStream;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.OutputStream;
24 import java.nio.ByteBuffer;
25 import java.util.Arrays;
26 import java.util.Date;
27 import java.util.HashSet;
28 import java.util.Set;
29 import java.util.zip.Deflater;
30 import java.util.zip.DeflaterOutputStream;
31 import java.util.zip.InflaterInputStream;
32
33 import com.healthmarketscience.jackcess.Column;
34 import com.healthmarketscience.jackcess.Row;
35 import com.healthmarketscience.jackcess.Table;
36 import com.healthmarketscience.jackcess.complex.Attachment;
37 import com.healthmarketscience.jackcess.complex.AttachmentColumnInfo;
38 import com.healthmarketscience.jackcess.complex.ComplexDataType;
39 import com.healthmarketscience.jackcess.complex.ComplexValue;
40 import com.healthmarketscience.jackcess.complex.ComplexValueForeignKey;
41 import com.healthmarketscience.jackcess.impl.ByteUtil;
42 import com.healthmarketscience.jackcess.impl.ColumnImpl;
43 import com.healthmarketscience.jackcess.impl.JetFormat;
44 import com.healthmarketscience.jackcess.impl.PageChannel;
45
46
47
48
49
50
51
52 public class AttachmentColumnInfoImpl extends ComplexColumnInfoImpl<Attachment>
53 implements AttachmentColumnInfo
54 {
55
56 private static final Set<String> COMPRESSED_FORMATS = new HashSet<String>(
57 Arrays.asList("jpg", "zip", "gz", "bz2", "z", "7z", "cab", "rar",
58 "mp3", "mpg"));
59
60 private static final String FILE_NAME_COL_NAME = "FileName";
61 private static final String FILE_TYPE_COL_NAME = "FileType";
62
63 private static final int DATA_TYPE_RAW = 0;
64 private static final int DATA_TYPE_COMPRESSED = 1;
65
66 private static final int UNKNOWN_HEADER_VAL = 1;
67 private static final int WRAPPER_HEADER_SIZE = 8;
68 private static final int CONTENT_HEADER_SIZE = 12;
69
70 private final Column _fileUrlCol;
71 private final Column _fileNameCol;
72 private final Column _fileTypeCol;
73 private final Column _fileDataCol;
74 private final Column _fileTimeStampCol;
75 private final Column _fileFlagsCol;
76
77 public AttachmentColumnInfoImpl(Column column, int complexId,
78 Table typeObjTable, Table flatTable)
79 throws IOException
80 {
81 super(column, complexId, typeObjTable, flatTable);
82
83 Column fileUrlCol = null;
84 Column fileNameCol = null;
85 Column fileTypeCol = null;
86 Column fileDataCol = null;
87 Column fileTimeStampCol = null;
88 Column fileFlagsCol = null;
89
90 for(Column col : getTypeColumns()) {
91 switch(col.getType()) {
92 case TEXT:
93 if(FILE_NAME_COL_NAME.equalsIgnoreCase(col.getName())) {
94 fileNameCol = col;
95 } else if(FILE_TYPE_COL_NAME.equalsIgnoreCase(col.getName())) {
96 fileTypeCol = col;
97 } else {
98
99 if(fileNameCol == null) {
100 fileNameCol = col;
101 } else if(fileTypeCol == null) {
102 fileTypeCol = col;
103 }
104 }
105 break;
106 case LONG:
107 fileFlagsCol = col;
108 break;
109 case SHORT_DATE_TIME:
110 fileTimeStampCol = col;
111 break;
112 case OLE:
113 fileDataCol = col;
114 break;
115 case MEMO:
116 fileUrlCol = col;
117 break;
118 default:
119
120 }
121 }
122
123 _fileUrlCol = fileUrlCol;
124 _fileNameCol = fileNameCol;
125 _fileTypeCol = fileTypeCol;
126 _fileDataCol = fileDataCol;
127 _fileTimeStampCol = fileTimeStampCol;
128 _fileFlagsCol = fileFlagsCol;
129 }
130
131 public Column getFileUrlColumn() {
132 return _fileUrlCol;
133 }
134
135 public Column getFileNameColumn() {
136 return _fileNameCol;
137 }
138
139 public Column getFileTypeColumn() {
140 return _fileTypeCol;
141 }
142
143 public Column getFileDataColumn() {
144 return _fileDataCol;
145 }
146
147 public Column getFileTimeStampColumn() {
148 return _fileTimeStampCol;
149 }
150
151 public Column getFileFlagsColumn() {
152 return _fileFlagsCol;
153 }
154
155 @Override
156 public ComplexDataType getType()
157 {
158 return ComplexDataType.ATTACHMENT;
159 }
160
161 @Override
162 protected AttachmentImpl toValue(ComplexValueForeignKey complexValueFk,
163 Row rawValue) {
164 ComplexValue.Id id = getValueId(rawValue);
165 String url = (String)getFileUrlColumn().getRowValue(rawValue);
166 String name = (String)getFileNameColumn().getRowValue(rawValue);
167 String type = (String)getFileTypeColumn().getRowValue(rawValue);
168 Integer flags = (Integer)getFileFlagsColumn().getRowValue(rawValue);
169 Date ts = (Date)getFileTimeStampColumn().getRowValue(rawValue);
170 byte[] data = (byte[])getFileDataColumn().getRowValue(rawValue);
171
172 return new AttachmentImpl(id, complexValueFk, url, name, type, null,
173 ts, flags, data);
174 }
175
176 @Override
177 protected Object[] asRow(Object[] row, Attachment attachment)
178 throws IOException
179 {
180 super.asRow(row, attachment);
181 getFileUrlColumn().setRowValue(row, attachment.getFileUrl());
182 getFileNameColumn().setRowValue(row, attachment.getFileName());
183 getFileTypeColumn().setRowValue(row, attachment.getFileType());
184 getFileFlagsColumn().setRowValue(row, attachment.getFileFlags());
185 getFileTimeStampColumn().setRowValue(row, attachment.getFileTimeStamp());
186 getFileDataColumn().setRowValue(row, attachment.getEncodedFileData());
187 return row;
188 }
189
190 public static Attachment newAttachment(byte[] data) {
191 return newAttachment(INVALID_FK, data);
192 }
193
194 public static Attachment newAttachment(ComplexValueForeignKey complexValueFk,
195 byte[] data) {
196 return newAttachment(complexValueFk, null, null, null, data, null, null);
197 }
198
199 public static Attachment newAttachment(
200 String url, String name, String type, byte[] data,
201 Date timeStamp, Integer flags)
202 {
203 return newAttachment(INVALID_FK, url, name, type, data,
204 timeStamp, flags);
205 }
206
207 public static Attachment newAttachment(
208 ComplexValueForeignKey complexValueFk, String url, String name,
209 String type, byte[] data, Date timeStamp, Integer flags)
210 {
211 return new AttachmentImpl(INVALID_ID, complexValueFk, url, name, type,
212 data, timeStamp, flags, null);
213 }
214
215 public static Attachment newEncodedAttachment(byte[] encodedData) {
216 return newEncodedAttachment(INVALID_FK, encodedData);
217 }
218
219 public static Attachment newEncodedAttachment(
220 ComplexValueForeignKey complexValueFk, byte[] encodedData) {
221 return newEncodedAttachment(complexValueFk, null, null, null, encodedData,
222 null, null);
223 }
224
225 public static Attachment newEncodedAttachment(
226 String url, String name, String type, byte[] encodedData,
227 Date timeStamp, Integer flags)
228 {
229 return newEncodedAttachment(INVALID_FK, url, name, type,
230 encodedData, timeStamp, flags);
231 }
232
233 public static Attachment newEncodedAttachment(
234 ComplexValueForeignKey complexValueFk, String url, String name,
235 String type, byte[] encodedData, Date timeStamp, Integer flags)
236 {
237 return new AttachmentImpl(INVALID_ID, complexValueFk, url, name, type,
238 null, timeStamp, flags, encodedData);
239 }
240
241
242 private static class AttachmentImpl extends ComplexValueImpl
243 implements Attachment
244 {
245 private String _url;
246 private String _name;
247 private String _type;
248 private byte[] _data;
249 private Date _timeStamp;
250 private Integer _flags;
251 private byte[] _encodedData;
252
253 private AttachmentImpl(Id id, ComplexValueForeignKey complexValueFk,
254 String url, String name, String type, byte[] data,
255 Date timeStamp, Integer flags, byte[] encodedData)
256 {
257 super(id, complexValueFk);
258 _url = url;
259 _name = name;
260 _type = type;
261 _data = data;
262 _timeStamp = timeStamp;
263 _flags = flags;
264 _encodedData = encodedData;
265 }
266
267 public byte[] getFileData() throws IOException {
268 if((_data == null) && (_encodedData != null)) {
269 _data = decodeData();
270 }
271 return _data;
272 }
273
274 public void setFileData(byte[] data) {
275 _data = data;
276 _encodedData = null;
277 }
278
279 public byte[] getEncodedFileData() throws IOException {
280 if((_encodedData == null) && (_data != null)) {
281 _encodedData = encodeData();
282 }
283 return _encodedData;
284 }
285
286 public void setEncodedFileData(byte[] data) {
287 _encodedData = data;
288 _data = null;
289 }
290
291 public String getFileName() {
292 return _name;
293 }
294
295 public void setFileName(String fileName) {
296 _name = fileName;
297 }
298
299 public String getFileUrl() {
300 return _url;
301 }
302
303 public void setFileUrl(String fileUrl) {
304 _url = fileUrl;
305 }
306
307 public String getFileType() {
308 return _type;
309 }
310
311 public void setFileType(String fileType) {
312 _type = fileType;
313 }
314
315 public Date getFileTimeStamp() {
316 return _timeStamp;
317 }
318
319 public void setFileTimeStamp(Date fileTimeStamp) {
320 _timeStamp = fileTimeStamp;
321 }
322
323 public Integer getFileFlags() {
324 return _flags;
325 }
326
327 public void setFileFlags(Integer fileFlags) {
328 _flags = fileFlags;
329 }
330
331 public void update() throws IOException {
332 getComplexValueForeignKey().updateAttachment(this);
333 }
334
335 public void delete() throws IOException {
336 getComplexValueForeignKey().deleteAttachment(this);
337 }
338
339 @Override
340 public String toString() {
341
342 String dataStr = null;
343 try {
344 dataStr = ByteUtil.toHexString(getFileData());
345 } catch(IOException e) {
346 dataStr = e.toString();
347 }
348
349 return "Attachment(" + getComplexValueForeignKey() + "," + getId() +
350 ") " + getFileUrl() + ", " + getFileName() + ", " + getFileType()
351 + ", " + getFileTimeStamp() + ", " + getFileFlags() + ", " +
352 dataStr;
353 }
354
355
356
357
358 private byte[] decodeData() throws IOException {
359
360 if(_encodedData.length < WRAPPER_HEADER_SIZE) {
361
362 throw new IOException("Unknown encoded attachment data format");
363 }
364
365
366 ByteBuffer bb = PageChannel.wrap(_encodedData);
367 int typeFlag = bb.getInt();
368 int dataLen = bb.getInt();
369
370 DataInputStream contentStream = null;
371 try {
372 InputStream bin = new ByteArrayInputStream(
373 _encodedData, WRAPPER_HEADER_SIZE,
374 _encodedData.length - WRAPPER_HEADER_SIZE);
375
376 if(typeFlag == DATA_TYPE_RAW) {
377
378 } else if(typeFlag == DATA_TYPE_COMPRESSED) {
379
380 bin = new InflaterInputStream(bin);
381 } else {
382 throw new IOException(
383 "Unknown encoded attachment data type " + typeFlag);
384 }
385
386 contentStream = new DataInputStream(bin);
387
388
389
390
391 byte[] tmpBytes = new byte[4];
392 contentStream.readFully(tmpBytes);
393 int headerLen = PageChannel.wrap(tmpBytes).getInt();
394 contentStream.skipBytes(headerLen - 4);
395
396
397
398 tmpBytes = new byte[dataLen - headerLen];
399 contentStream.readFully(tmpBytes);
400
401 return tmpBytes;
402
403 } finally {
404 ByteUtil.closeQuietly(contentStream);
405 }
406 }
407
408
409
410
411 private byte[] encodeData() throws IOException {
412
413
414 String type = ((_type != null) ? _type.toLowerCase() : "");
415 boolean shouldCompress = !COMPRESSED_FORMATS.contains(type);
416
417
418 type += '\0';
419 ByteBuffer typeBytes = ColumnImpl.encodeUncompressedText(
420 type, JetFormat.VERSION_12.CHARSET);
421 int headerLen = typeBytes.remaining() + CONTENT_HEADER_SIZE;
422
423 int dataLen = _data.length;
424 ByteUtil.ByteStream dataStream = new ByteUtil.ByteStream(
425 WRAPPER_HEADER_SIZE + headerLen + dataLen);
426
427
428 ByteBuffer bb = PageChannel.wrap(dataStream.getBytes());
429 bb.putInt(shouldCompress ? DATA_TYPE_COMPRESSED : DATA_TYPE_RAW);
430 bb.putInt(dataLen + headerLen);
431 dataStream.skip(WRAPPER_HEADER_SIZE);
432
433 OutputStream contentStream = dataStream;
434 Deflater deflater = null;
435 try {
436
437 if(shouldCompress) {
438 contentStream = new DeflaterOutputStream(
439 contentStream, deflater = new Deflater(3));
440 }
441
442
443 byte[] tmpBytes = new byte[CONTENT_HEADER_SIZE];
444 PageChannel.wrap(tmpBytes)
445 .putInt(headerLen)
446 .putInt(UNKNOWN_HEADER_VAL)
447 .putInt(type.length());
448 contentStream.write(tmpBytes);
449 contentStream.write(typeBytes.array(), 0, typeBytes.remaining());
450
451
452 contentStream.write(_data);
453 contentStream.close();
454 contentStream = null;
455
456 return dataStream.toByteArray();
457
458 } finally {
459 ByteUtil.closeQuietly(contentStream);
460 if(deflater != null) {
461 deflater.end();
462 }
463 }
464 }
465 }
466
467 }