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.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() throws IOException;
161 
162     public String getClassName() throws IOException;
163 
164     public String getTypeName() throws IOException;
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) throws IOException {
423       return OleUtil.parseBlob(bytes);
424     }
425   }
426 }