1 /*
2 Copyright (c) 2013 James Ahlborn
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16
17 package com.healthmarketscience.jackcess.util;
18
19 import java.io.Closeable;
20 import java.io.File;
21 import java.io.FileInputStream;
22 import java.io.FileNotFoundException;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.OutputStream;
26 import java.sql.Blob;
27 import java.util.stream.Stream;
28 import java.util.stream.StreamSupport;
29
30 import com.healthmarketscience.jackcess.impl.OleUtil;
31
32 /**
33 * Extensions of the Blob interface with additional functionality for working
34 * with the OLE content from an access database. The ole data type in access
35 * has a wide range of functionality (including wrappers with nested wrappers
36 * with nested filesystems!), and jackcess only supports a small portion of
37 * it. That said, jackcess should support the bulk of the common
38 * functionality.
39 * <p>
40 * The main Blob methods will interact with the <i>entire</i> OLE field data
41 * which, in most cases, contains additional wrapper information. In order to
42 * access the ultimate "content" contained within the OLE data, the {@link
43 * #getContent} method should be used. The type of this content may be a
44 * variety of formats, so additional sub-interfaces are available to interact
45 * with it. The most specific sub-interface can be determined by the {@link
46 * ContentType} of the Content.
47 * <p>
48 * Once an OleBlob is no longer useful, <i>it should be closed</i> using
49 * {@link #free} or {@link #close} methods (after which, the instance will no
50 * longer be functional).
51 * <p>
52 * Note, the OleBlob implementation is read-only (through the interface). In
53 * order to modify blob contents, create a new OleBlob instance using {@link
54 * OleBlob.Builder} and write it to the access database.
55 * <p>
56 * <b>Example for interpreting an existing OLE field:</b>
57 * <pre>
58 * OleBlob oleBlob = null;
59 * try {
60 * oleBlob = row.getBlob("MyOleColumn");
61 * Content content = oleBlob.getContent()
62 * if(content.getType() == OleBlob.ContentType.SIMPLE_PACKAGE) {
63 * FileOutputStream out = ...;
64 * ((SimplePackageContent)content).writeTo(out);
65 * out.closee();
66 * }
67 * } finally {
68 * if(oleBlob != null) { oleBlob.close(); }
69 * }
70 * </pre>
71 * <p>
72 * <b>Example for creating new, embedded ole data:</b>
73 * <pre>
74 * OleBlob oleBlob = null;
75 * try {
76 * oleBlob = new OleBlob.Builder()
77 * .setSimplePackage(new File("some_data.txt"))
78 * .toBlob();
79 * db.addRow(1, oleBlob);
80 * } finally {
81 * if(oleBlob != null) { oleBlob.close(); }
82 * }
83 * </pre>
84 * <p>
85 * <b>Example for creating new, linked ole data:</b>
86 * <pre>
87 * OleBlob oleBlob = null;
88 * try {
89 * oleBlob = new OleBlob.Builder()
90 * .setLink(new File("some_data.txt"))
91 * .toBlob();
92 * db.addRow(1, oleBlob);
93 * } finally {
94 * if(oleBlob != null) { oleBlob.close(); }
95 * }
96 * </pre>
97 *
98 * @author James Ahlborn
99 */
100 public interface OleBlob extends Blob, Closeable
101 {
102 /** Enum describing the types of blob contents which are currently
103 supported/understood */
104 public enum ContentType {
105 /** the blob contents are a link (file path) to some external content.
106 Content will be an instance of LinkContent */
107 LINK,
108 /** the blob contents are a simple wrapper around some embedded content
109 and related file names/paths. Content will be an instance
110 SimplePackageContent */
111 SIMPLE_PACKAGE,
112 /** the blob contents are a complex embedded data known as compound
113 storage (aka OLE2). Working with compound storage requires the
114 optional POI library. Content will be an instance of CompoundContent.
115 If the POI library is not available on the classpath, then compound
116 storage data will instead be returned as type {@link #OTHER}. */
117 COMPOUND_STORAGE,
118 /** the top-level blob wrapper is understood, but the nested blob contents
119 are unknown, probably just some embedded content. Content will be an
120 instance of OtherContent */
121 OTHER,
122 /** the top-level blob wrapper is not understood (this may not be a valid
123 ole instance). Content will simply be an instance of Content (the
124 data can be accessed from the main blob instance) */
125 UNKNOWN;
126 }
127
128 /**
129 * Writes the entire raw blob data to the given stream (this is the access
130 * db internal format, which includes all wrapper information).
131 *
132 * @param out stream to which the blob will be written
133 */
134 public void writeTo(OutputStream out) throws IOException;
135
136 /**
137 * Returns the decoded form of the blob contents, if understandable.
138 */
139 public Content getContent() throws IOException;
140
141
142 public interface Content
143 {
144 /**
145 * Returns the type of this content.
146 */
147 public ContentType getType();
148
149 /**
150 * Returns the blob which owns this content.
151 */
152 public OleBlob getBlob();
153 }
154
155 /**
156 * Intermediate sub-interface for Content which has a nested package.
157 */
158 public interface PackageContent extends Content
159 {
160 public String getPrettyName();
161
162 public String getClassName();
163
164 public String getTypeName();
165 }
166
167 /**
168 * Intermediate sub-interface for Content which has embedded content.
169 */
170 public interface EmbeddedContent extends Content
171 {
172 public long length();
173
174 public InputStream getStream() throws IOException;
175
176 public void writeTo(OutputStream out) throws IOException;
177 }
178
179 /**
180 * Sub-interface for Content which has the {@link ContentType#LINK} type.
181 * The actual content is external to the access database and can be found at
182 * {@link #getLinkPath}.
183 */
184 public interface LinkContent extends PackageContent
185 {
186 public String getFileName();
187
188 public String getLinkPath();
189
190 public String getFilePath();
191
192 public InputStream getLinkStream() throws IOException;
193 }
194
195 /**
196 * Sub-interface for Content which has the {@link
197 * ContentType#SIMPLE_PACKAGE} type. The actual content is embedded within
198 * the access database (but the original file source path can also be found
199 * at {@link #getFilePath}).
200 */
201 public interface SimplePackageContent
202 extends PackageContent, EmbeddedContent
203 {
204 public String getFileName();
205
206 public String getFilePath();
207
208 public String getLocalFilePath();
209 }
210
211 /**
212 * Sub-interface for Content which has the {@link
213 * ContentType#COMPOUND_STORAGE} type. Compound storage is a complex
214 * embedding format also known as OLE2. In some situations (mostly
215 * non-microsoft office file formats) the actual content is available from
216 * the {@link #getContentsEntry} method (if {@link #hasContentsEntry}
217 * returns {@code true}). In other situations (e.g. microsoft office file
218 * formats), the actual content is most or all of the compound content (but
219 * retrieving the final file may be a complex operation beyond the scope of
220 * jackcess). Note that the CompoundContent type will only be available if
221 * the POI library is in the classpath, otherwise compound content will be
222 * returned as OtherContent.
223 */
224 public interface CompoundContent extends PackageContent, EmbeddedContent,
225 Iterable<CompoundContent.Entry>
226 {
227 public Entry getEntry(String entryName) throws IOException;
228
229 public boolean hasContentsEntry() throws IOException;
230
231 public Entry getContentsEntry() throws IOException;
232
233 /**
234 * @return a Stream using the default Iterator.
235 */
236 default public Stream<CompoundContent.Entry> stream() {
237 return StreamSupport.stream(spliterator(), false);
238 }
239
240 /**
241 * A document entry in the compound storage.
242 */
243 public interface Entry extends EmbeddedContent
244 {
245 public String getName();
246
247 /**
248 * Returns the CompoundContent which owns this entry.
249 */
250 public CompoundContent getParent();
251 }
252 }
253
254 /**
255 * Sub-interface for Content which has the {@link ContentType#OTHER} type.
256 * This may be a simple embedded file or some other, currently not
257 * understood complex type.
258 */
259 public interface OtherContent extends PackageContent, EmbeddedContent
260 {
261 }
262
263 /**
264 * Builder style class for constructing an OleBlob. See {@link OleBlob} for
265 * example usage.
266 */
267 public class Builder
268 {
269 public static final String PACKAGE_PRETTY_NAME = "Packager Shell Object";
270 public static final String PACKAGE_TYPE_NAME = "Package";
271
272 private ContentType _type;
273 private byte[] _bytes;
274 private InputStream _stream;
275 private long _contentLen;
276 private String _fileName;
277 private String _filePath;
278 private String _prettyName;
279 private String _className;
280 private String _typeName;
281
282 public ContentType getType() {
283 return _type;
284 }
285
286 public byte[] getBytes() {
287 return _bytes;
288 }
289
290 public InputStream getStream() {
291 return _stream;
292 }
293
294 public long getContentLength() {
295 return _contentLen;
296 }
297
298 public String getFileName() {
299 return _fileName;
300 }
301
302 public String getFilePath() {
303 return _filePath;
304 }
305
306 public String getPrettyName() {
307 return _prettyName;
308 }
309
310 public String getClassName() {
311 return _className;
312 }
313
314 public String getTypeName() {
315 return _typeName;
316 }
317
318 public Builder setSimplePackageBytes(byte[] bytes) {
319 _bytes = bytes;
320 _contentLen = bytes.length;
321 setDefaultPackageType();
322 _type = ContentType.SIMPLE_PACKAGE;
323 return this;
324 }
325
326 public Builder setSimplePackageStream(InputStream in, long length) {
327 _stream = in;
328 _contentLen = length;
329 setDefaultPackageType();
330 _type = ContentType.SIMPLE_PACKAGE;
331 return this;
332 }
333
334 public Builder setSimplePackageFileName(String fileName) {
335 _fileName = fileName;
336 setDefaultPackageType();
337 _type = ContentType.SIMPLE_PACKAGE;
338 return this;
339 }
340
341 public Builder setSimplePackageFilePath(String filePath) {
342 _filePath = filePath;
343 setDefaultPackageType();
344 _type = ContentType.SIMPLE_PACKAGE;
345 return this;
346 }
347
348 public Builder setSimplePackage(File f) throws FileNotFoundException {
349 _fileName = f.getName();
350 _filePath = f.getAbsolutePath();
351 return setSimplePackageStream(new FileInputStream(f), f.length());
352 }
353
354 public Builder setLinkFileName(String fileName) {
355 _fileName = fileName;
356 setDefaultPackageType();
357 _type = ContentType.LINK;
358 return this;
359 }
360
361 public Builder setLinkPath(String link) {
362 _filePath = link;
363 setDefaultPackageType();
364 _type = ContentType.LINK;
365 return this;
366 }
367
368 public Builder setLink(File f) {
369 _fileName = f.getName();
370 _filePath = f.getAbsolutePath();
371 setDefaultPackageType();
372 _type = ContentType.LINK;
373 return this;
374 }
375
376 private void setDefaultPackageType() {
377 if(_prettyName == null) {
378 _prettyName = PACKAGE_PRETTY_NAME;
379 }
380 if(_className == null) {
381 _className = PACKAGE_TYPE_NAME;
382 }
383 }
384
385 public Builder setOtherBytes(byte[] bytes) {
386 _bytes = bytes;
387 _contentLen = bytes.length;
388 _type = ContentType.OTHER;
389 return this;
390 }
391
392 public Builder setOtherStream(InputStream in, long length) {
393 _stream = in;
394 _contentLen = length;
395 _type = ContentType.OTHER;
396 return this;
397 }
398
399 public Builder setOther(File f) throws FileNotFoundException {
400 return setOtherStream(new FileInputStream(f), f.length());
401 }
402
403 public Builder setPackagePrettyName(String prettyName) {
404 _prettyName = prettyName;
405 return this;
406 }
407
408 public Builder setPackageClassName(String className) {
409 _className = className;
410 return this;
411 }
412
413 public Builder setPackageTypeName(String typeName) {
414 _typeName = typeName;
415 return this;
416 }
417
418 public OleBlob toBlob() throws IOException {
419 return OleUtil.createBlob(this);
420 }
421
422 public static OleBlob fromInternalData(byte[] bytes) {
423 return OleUtil.parseBlob(bytes);
424 }
425 }
426 }