View Javadoc
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 }