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