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.impl;
18  
19  import java.io.FileNotFoundException;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.OutputStream;
23  import java.io.UnsupportedEncodingException;
24  import java.net.URLDecoder;
25  import java.net.URLEncoder;
26  import java.nio.ByteBuffer;
27  import java.util.ArrayList;
28  import java.util.Iterator;
29  import java.util.List;
30  
31  import com.healthmarketscience.jackcess.RuntimeIOException;
32  import static com.healthmarketscience.jackcess.impl.OleUtil.*;
33  import com.healthmarketscience.jackcess.util.MemFileChannel;
34  import static com.healthmarketscience.jackcess.util.OleBlob.*;
35  import org.apache.commons.lang3.builder.ToStringBuilder;
36  import org.apache.poi.poifs.filesystem.DirectoryEntry;
37  import org.apache.poi.poifs.filesystem.DocumentEntry;
38  import org.apache.poi.poifs.filesystem.DocumentInputStream;
39  import org.apache.poi.poifs.filesystem.POIFSFileSystem;
40  
41  /**
42   * Utility code for working with OLE data which is in the compound storage
43   * format.  This functionality relies on the optional POI library.
44   * <p>
45   * Note that all POI usage is restricted to this file so that the basic ole
46   * support in OleUtil can be utilized without requiring POI.
47   *
48   * @author James Ahlborn
49   * @usage _advanced_class_
50   */
51  public class CompoundOleUtil implements CompoundPackageFactory
52  {
53    private static final String ENTRY_NAME_CHARSET = "UTF-8";
54    private static final String ENTRY_SEPARATOR = "/";
55    private static final String CONTENTS_ENTRY = "CONTENTS";
56  
57    static {
58      // force a poi class to be loaded to ensure that when this class is
59      // loaded, we know that the poi classes are available
60      POIFSFileSystem.class.getName();
61    }
62  
63    public CompoundOleUtil()
64    {
65    }
66  
67    /**
68     * Creates a nes CompoundContent for the given blob information.
69     */
70    @Override
71    public ContentImpl createCompoundPackageContent(
72        OleBlobImpl blob, String prettyName, String className, String typeName,
73        ByteBuffer blobBb, int dataBlockLen)
74    {
75      return new CompoundContentImpl(blob, prettyName, className, typeName,
76                                     blobBb.position(), dataBlockLen);
77    }
78  
79    /**
80     * Gets a DocumentEntry from compound storage based on a fully qualified,
81     * encoded entry name.
82     *
83     * @param entryName fully qualified, encoded entry name
84     * @param dir root directory of the compound storage
85     *
86     * @return the relevant DocumentEntry
87     * @throws FileNotFoundException if the entry does not exist
88     * @throws IOException if some other io error occurs
89     */
90    public static DocumentEntry getDocumentEntry(String entryName,
91                                                 DirectoryEntry dir)
92      throws IOException
93    {
94      // split entry name into individual components and decode them
95      List<String> entryNames = new ArrayList<String>();
96      for(String str : entryName.split(ENTRY_SEPARATOR)) {
97        if(str.length() == 0) {
98          continue;
99        }
100       entryNames.add(decodeEntryName(str));
101     }
102 
103     DocumentEntry entry = null;
104     Iterator<String> iter = entryNames.iterator();
105     while(iter.hasNext()) {
106       org.apache.poi.poifs.filesystem.Entry tmpEntry = dir.getEntry(iter.next());
107       if(tmpEntry instanceof DirectoryEntry) {
108         dir = (DirectoryEntry)tmpEntry;
109       } else if(!iter.hasNext() && (tmpEntry instanceof DocumentEntry)) {
110         entry = (DocumentEntry)tmpEntry;
111       } else {
112         break;
113       }
114     }
115 
116     if(entry == null) {
117       throw new FileNotFoundException("Could not find document " + entryName);
118     }
119 
120     return entry;
121   }
122 
123   private static String encodeEntryName(String name) {
124     try {
125       return URLEncoder.encode(name, ENTRY_NAME_CHARSET);
126     } catch(UnsupportedEncodingException e) {
127       throw new RuntimeException(e);
128     }
129   }
130 
131   private static String decodeEntryName(String name) {
132     try {
133       return URLDecoder.decode(name, ENTRY_NAME_CHARSET);
134     } catch(UnsupportedEncodingException e) {
135       throw new RuntimeException(e);
136     }
137   }
138 
139   private static final class CompoundContentImpl
140     extends EmbeddedPackageContentImpl
141     implements CompoundContent
142   {
143     private POIFSFileSystem _fs;
144 
145     private CompoundContentImpl(
146         OleBlobImpl blob, String prettyName, String className,
147         String typeName, int position, int length)
148     {
149       super(blob, prettyName, className, typeName, position, length);
150     }
151 
152     @Override
153     public ContentType getType() {
154       return ContentType.COMPOUND_STORAGE;
155     }
156 
157     private POIFSFileSystem getFileSystem() throws IOException {
158       if(_fs == null) {
159         _fs = new POIFSFileSystem(MemFileChannel.newChannel(getStream(), "r"));
160       }
161       return _fs;
162     }
163 
164     @Override
165     public Iterator<Entry> iterator() {
166       try {
167       return getEntries(new ArrayList<Entry>(), getFileSystem().getRoot(),
168                         ENTRY_SEPARATOR).iterator();
169       } catch(IOException e) {
170         throw new RuntimeIOException(e);
171       }
172     }
173 
174     @Override
175     public EntryImpl getEntry(String entryName) throws IOException {
176       return new EntryImpl(entryName,
177                            getDocumentEntry(entryName, getFileSystem().getRoot()));
178     }
179 
180     @Override
181     public boolean hasContentsEntry() throws IOException {
182       return getFileSystem().getRoot().hasEntry(CONTENTS_ENTRY);
183     }
184 
185     @Override
186     public EntryImpl getContentsEntry() throws IOException {
187       return getEntry(CONTENTS_ENTRY);
188     }
189 
190     private List<Entry> getEntries(List<Entry> entries, DirectoryEntry dir,
191                                    String prefix) {
192       for(org.apache.poi.poifs.filesystem.Entry entry : dir) {
193         if (entry instanceof DirectoryEntry) {
194           // .. recurse into this directory
195           getEntries(entries, (DirectoryEntry)entry, prefix + ENTRY_SEPARATOR);
196         } else if(entry instanceof DocumentEntry) {
197           // grab the entry name/detils
198           DocumentEntry de = (DocumentEntry)entry;
199           String entryName = prefix + encodeEntryName(entry.getName());
200           entries.add(new EntryImpl(entryName, de));
201         }
202       }
203       return entries;
204     }
205 
206     @Override
207     public void close() {
208       ByteUtil.closeQuietly(_fs);
209       _fs = null;
210       super.close();
211     }
212 
213     @Override
214     public String toString() {
215       ToStringBuilder sb = toString(CustomToStringStyle.builder(this));
216 
217       try {
218         sb.append("hasContentsEntry", hasContentsEntry());
219         sb.append("entries", getEntries(new ArrayList<Entry>(),
220                                         getFileSystem().getRoot(),
221                                         ENTRY_SEPARATOR));
222       } catch(IOException e) {
223         sb.append("entries", "<" + e + ">");
224       }
225 
226       return sb.toString();
227     }
228 
229     private final class EntryImpl implements CompoundContent.Entry
230     {
231       private final String _name;
232       private final DocumentEntry _docEntry;
233 
234       private EntryImpl(String name, DocumentEntry docEntry) {
235         _name = name;
236         _docEntry = docEntry;
237       }
238 
239       @Override
240       public ContentType getType() {
241         return ContentType.UNKNOWN;
242       }
243 
244       @Override
245       public String getName() {
246         return _name;
247       }
248 
249       @Override
250       public CompoundContentImpl getParent() {
251         return CompoundContentImpl.this;
252       }
253 
254       @Override
255       public OleBlobImpl getBlob() {
256         return getParent().getBlob();
257       }
258 
259       @Override
260       public long length() {
261         return _docEntry.getSize();
262       }
263 
264       @Override
265       public InputStream getStream() throws IOException {
266         return new DocumentInputStream(_docEntry);
267       }
268 
269       @Override
270       public void writeTo(OutputStream out) throws IOException {
271         InputStream in = null;
272         try {
273           ByteUtil.copy(in = getStream(), out);
274         } finally {
275           ByteUtil.closeQuietly(in);
276         }
277       }
278 
279       @Override
280       public String toString() {
281         return CustomToStringStyle.valueBuilder(this)
282           .append("name", _name)
283           .append("length", length())
284           .toString();
285       }
286     }
287   }
288 
289 }