Browse Source

initial support for blob creation/parsing

git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@797 f203690c-595d-4dc9-a70b-905162fa7fd2
tags/jackcess-2.0.1
James Ahlborn 10 years ago
parent
commit
966b10b933

+ 10
- 1
pom.xml View File

@@ -224,7 +224,16 @@
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependency>

<!-- Only necessary if working with compound ole data -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.9</version>
<optional>true</optional>
</dependency>
</dependencies>
<reporting>
<plugins>

+ 5
- 0
src/changes/changes.xml View File

@@ -4,6 +4,11 @@
<author email="javajedi@users.sf.net">Tim McCune</author>
</properties>
<body>
<release version="2.0.1" date="TBD">
<action dev="jahlborn" type="add">
Add initial support for creating/parsing ole content.
</action>
</release>
<release version="2.0.0" date="2013-08-26">
<action dev="jahlborn" type="update">
Brand new API! This release is not backwards compatible with 1.x

+ 2
- 0
src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java View File

@@ -1709,6 +1709,8 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> {
return null;
} else if(value instanceof byte[]) {
return (byte[])value;
} else if(value instanceof OleUtil.OleBlobImpl) {
return ((OleUtil.OleBlobImpl)value).getBytes();
} else if(value instanceof Blob) {
try {
Blob b = (Blob)value;

+ 202
- 0
src/main/java/com/healthmarketscience/jackcess/impl/CompoundOleUtil.java View File

@@ -0,0 +1,202 @@
/*
Copyright (c) 2013 James Ahlborn

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA
*/

package com.healthmarketscience.jackcess.impl;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import static com.healthmarketscience.jackcess.impl.OleUtil.*;
import com.healthmarketscience.jackcess.util.MemFileChannel;
import static com.healthmarketscience.jackcess.util.OleBlob.*;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.poi.poifs.filesystem.DirectoryEntry;
import org.apache.poi.poifs.filesystem.DocumentEntry;
import org.apache.poi.poifs.filesystem.DocumentInputStream;
import org.apache.poi.poifs.filesystem.Entry;
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;

/**
* Utility code for working with OLE data which is in the compound storage
* format. This functionality relies on the optional POI library.
* <p/>
* Note that all POI usage is restricted to this file so that the basic ole
* support in OleUtil can be utilized without requiring POI.
*
* @author James Ahlborn
*/
public class CompoundOleUtil implements OleUtil.CompoundPackageFactory
{
private static final String ENTRY_NAME_CHARSET = "UTF-8";
private static final String ENTRY_SEPARATOR = "/";
private static final String CONTENTS_ENTRY = "CONTENTS";

public CompoundOleUtil()
{
}

public ContentImpl createCompoundPackageContent(
OleBlobImpl blob, String prettyName, String className, String typeName,
ByteBuffer blobBb, int dataBlockLen)
{
return new CompoundContentImpl(blob, prettyName, className, typeName,
blobBb.position(), dataBlockLen);
}

private static String encodeEntryName(String name) {
try {
return URLEncoder.encode(name, ENTRY_NAME_CHARSET);
} catch(UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}

private static String decodeEntryName(String name) {
try {
return URLDecoder.decode(name, ENTRY_NAME_CHARSET);
} catch(UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}

private static final class CompoundContentImpl
extends EmbeddedPackageContentImpl
implements CompoundContent
{
private NPOIFSFileSystem _fs;

private CompoundContentImpl(
OleBlobImpl blob, String prettyName, String className,
String typeName, int position, int length)
{
super(blob, prettyName, className, typeName, position, length);
}

public ContentType getType() {
return ContentType.COMPOUND_STORAGE;
}

private NPOIFSFileSystem getFileSystem() throws IOException {
if(_fs == null) {
_fs = new NPOIFSFileSystem(MemFileChannel.newChannel(getStream(), "r"));
}
return _fs;
}

public List<String> getEntries() throws IOException {
return getEntries(new ArrayList<String>(), getFileSystem().getRoot(),
ENTRY_SEPARATOR, false);
}

public InputStream getEntryStream(String entryName) throws IOException {
return new DocumentInputStream(getDocumentEntry(entryName));
}

public boolean hasContentsEntry() throws IOException {
return getFileSystem().getRoot().hasEntry(CONTENTS_ENTRY);
}

public InputStream getContentsEntryStream() throws IOException {
return getEntryStream(CONTENTS_ENTRY);
}

private DocumentEntry getDocumentEntry(String entryName) throws IOException {

// split entry name into individual components and decode them
List<String> entryNames = new ArrayList<String>();
for(String str : entryName.split(ENTRY_SEPARATOR)) {
if(str.length() == 0) {
continue;
}
entryNames.add(decodeEntryName(str));
}

DirectoryEntry dir = getFileSystem().getRoot();
DocumentEntry entry = null;
Iterator<String> iter = entryNames.iterator();
while(iter.hasNext()) {
Entry tmpEntry = dir.getEntry(iter.next());
if(tmpEntry instanceof DirectoryEntry) {
dir = (DirectoryEntry)tmpEntry;
} else if(!iter.hasNext() && (tmpEntry instanceof DocumentEntry)) {
entry = (DocumentEntry)tmpEntry;
} else {
break;
}
}
if(entry == null) {
throw new FileNotFoundException("Could not find document " + entryName);
}

return entry;
}

private List<String> getEntries(List<String> entries, DirectoryEntry dir,
String prefix, boolean includeDetails) {
for(Entry entry : dir) {
if (entry instanceof DirectoryEntry) {
// .. recurse into this directory
getEntries(entries, (DirectoryEntry)entry, prefix + ENTRY_SEPARATOR,
includeDetails);
} else if(entry instanceof DocumentEntry) {
// grab the entry name/detils
String entryName = prefix + encodeEntryName(entry.getName());
if(includeDetails) {
entryName += " (" + ((DocumentEntry)entry).getSize() + ")";
}
entries.add(entryName);
}
}
return entries;
}

@Override
public void close() {
ByteUtil.closeQuietly(_fs);
_fs = null;
super.close();
}

@Override
public String toString() {
ToStringBuilder sb = toString(CustomToStringStyle.builder(this));

try {
sb.append("hasContentsEntry", hasContentsEntry());
sb.append("entries",
getEntries(new ArrayList<String>(), getFileSystem().getRoot(),
ENTRY_SEPARATOR, true));
} catch(IOException e) {
sb.append("entries", "<" + e + ">");
}

return sb.toString();
}
}

}

+ 1
- 1
src/main/java/com/healthmarketscience/jackcess/impl/IndexPageCache.java View File

@@ -1125,7 +1125,7 @@ public class IndexPageCache
}
}
} catch(IOException e) {
pages.add("DataPage[" + dpMain._pageNumber + "]: " + e);
pages.add("DataPage[" + dpMain._pageNumber + "]: <" + e + ">");
}
return pages;
}

+ 778
- 0
src/main/java/com/healthmarketscience/jackcess/impl/OleUtil.java View File

@@ -0,0 +1,778 @@
/*
Copyright (c) 2013 James Ahlborn

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA
*/

package com.healthmarketscience.jackcess.impl;

import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.sql.Blob;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.EnumSet;
import java.util.Set;

import com.healthmarketscience.jackcess.DataType;
import com.healthmarketscience.jackcess.util.OleBlob;
import static com.healthmarketscience.jackcess.util.OleBlob.*;
import org.apache.commons.lang.builder.ToStringBuilder;

/**
* Utility code for working with OLE data.
*
* @author James Ahlborn
*/
public class OleUtil
{
/**
* Interface used to allow optional inclusion of the poi library for working
* with compound ole data.
*/
public interface CompoundPackageFactory
{
public ContentImpl createCompoundPackageContent(
OleBlobImpl blob, String prettyName, String className, String typeName,
ByteBuffer blobBb, int dataBlockLen);
}

private static final int PACKAGE_SIGNATURE = 0x1C15;
private static final Charset OLE_CHARSET = Charset.forName("US-ASCII");
private static final Charset OLE_UTF_CHARSET = Charset.forName("UTF-16LE");
private static final byte[] COMPOUND_STORAGE_SIGNATURE =
{(byte)0xd0,(byte)0xcf,(byte)0x11,(byte)0xe0,
(byte)0xa1,(byte)0xb1,(byte)0x1a,(byte)0xe1};
private static final String SIMPLE_PACKAGE_TYPE = "Package";
private static final int PACKAGE_OBJECT_TYPE = 0x02;
private static final int OLE_VERSION = 0x0501;
private static final int OLE_FORMAT = 0x02;
private static final int PACKAGE_STREAM_SIGNATURE = 0x02;
private static final int PS_EMBEDDED_FILE = 0x030000;
private static final int PS_LINKED_FILE = 0x010000;
private static final Set<ContentType> WRITEABLE_TYPES = EnumSet.of(
ContentType.LINK, ContentType.SIMPLE_PACKAGE, ContentType.OTHER);
private static final byte[] NO_DATA = new byte[0];
private static final int LINK_HEADER = 0x01;
private static final byte[] PACKAGE_FOOTER = {
0x01, 0x05, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x01, (byte)0xAD, 0x05, (byte)0xFE
};

private static final CompoundPackageFactory COMPOUND_FACTORY;

static {
CompoundPackageFactory compoundFactory = null;
try {
compoundFactory = (CompoundPackageFactory)
Class.forName("com.healthmarketscience.jackcess.impl.CompoundOleUtil")
.newInstance();
} catch(Exception e) {
// must not have poi, will load compound ole data as "other"
}
COMPOUND_FACTORY = compoundFactory;
}

public static OleBlob parseBlob(byte[] bytes) {
return new OleBlobImpl(bytes);
}

public static OleBlob createBlob(OleBlob.Builder oleBuilder)
throws IOException
{
try {
if(!WRITEABLE_TYPES.contains(oleBuilder.getType())) {
throw new IllegalArgumentException(
"Cannot currently create ole values of type " +
oleBuilder.getType());
}
long contentLen = oleBuilder.getContentLength();
byte[] contentBytes = oleBuilder.getBytes();
InputStream contentStream = oleBuilder.getStream();
byte[] packageStreamHeader = NO_DATA;
byte[] packageStreamFooter = NO_DATA;

switch(oleBuilder.getType()) {
case LINK:
packageStreamHeader = writePackageStreamHeader(oleBuilder);

// link "content" is file path
contentBytes = getZeroTermStrBytes(oleBuilder.getFilePath());
contentLen = contentBytes.length;
break;
case SIMPLE_PACKAGE:
packageStreamHeader = writePackageStreamHeader(oleBuilder);
packageStreamFooter = writePackageStreamFooter(oleBuilder);
break;
case OTHER:
// nothing more to do
break;
default:
throw new RuntimeException("unexpected type " + oleBuilder.getType());
}

long payloadLen = packageStreamHeader.length + packageStreamFooter.length +
contentLen;
byte[] packageHeader = writePackageHeader(oleBuilder, payloadLen);
long totalOleLen = packageHeader.length + PACKAGE_FOOTER.length +
payloadLen;
if(totalOleLen > DataType.OLE.getMaxSize()) {
throw new IllegalArgumentException("Content size of " + totalOleLen +
" is too large for ole column");
}
byte[] oleBytes = new byte[(int)totalOleLen];
ByteBuffer bb = PageChannel.wrap(oleBytes);
bb.put(packageHeader);
bb.put(packageStreamHeader);
if(contentLen > 0L) {
if(contentBytes != null) {
bb.put(contentBytes);
} else {
byte[] buf = new byte[8192];
int numBytes = 0;
while((numBytes = contentStream.read(buf)) >= 0) {
bb.put(buf, 0, numBytes);
}
}
}

bb.put(packageStreamFooter);
bb.put(PACKAGE_FOOTER);
return parseBlob(oleBytes);
} finally {
ByteUtil.closeQuietly(oleBuilder.getStream());
}
}

private static byte[] writePackageHeader(OleBlob.Builder oleBuilder,
long contentLen) {

byte[] prettyNameBytes = getZeroTermStrBytes(oleBuilder.getPrettyName());
String className = oleBuilder.getClassName();
String typeName = oleBuilder.getTypeName();
if(className == null) {
className = typeName;
} else if(typeName == null) {
typeName = className;
}
byte[] classNameBytes = getZeroTermStrBytes(className);
byte[] typeNameBytes = getZeroTermStrBytes(typeName);
int packageHeaderLen = 20 + prettyNameBytes.length + classNameBytes.length;

int oleHeaderLen = 24 + typeNameBytes.length;

byte[] headerBytes = new byte[packageHeaderLen + oleHeaderLen];
ByteBuffer bb = PageChannel.wrap(headerBytes);

// write outer package header
bb.putShort((short)PACKAGE_SIGNATURE);
bb.putShort((short)packageHeaderLen);
bb.putInt(PACKAGE_OBJECT_TYPE);
bb.putShort((short)prettyNameBytes.length);
bb.putShort((short)classNameBytes.length);
int prettyNameOff = bb.position() + 8;
bb.putShort((short)prettyNameOff);
bb.putShort((short)(prettyNameOff + prettyNameBytes.length));
bb.putInt(-1);
bb.put(prettyNameBytes);
bb.put(classNameBytes);

// put ole header
bb.putInt(OLE_VERSION);
bb.putInt(OLE_FORMAT);
bb.putInt(typeNameBytes.length);
bb.put(typeNameBytes);
bb.putLong(0L);
bb.putInt((int)contentLen);
return headerBytes;
}

private static byte[] writePackageStreamHeader(OleBlob.Builder oleBuilder) {

byte[] fileNameBytes = getZeroTermStrBytes(oleBuilder.getFileName());
byte[] filePathBytes = getZeroTermStrBytes(oleBuilder.getFilePath());

int headerLen = 6 + fileNameBytes.length + filePathBytes.length;

if(oleBuilder.getType() == ContentType.SIMPLE_PACKAGE) {

headerLen += 8 + filePathBytes.length;
} else {

headerLen += 2;
}

byte[] headerBytes = new byte[headerLen];
ByteBuffer bb = PageChannel.wrap(headerBytes);
bb.putShort((short)PACKAGE_STREAM_SIGNATURE);
bb.put(fileNameBytes);
bb.put(filePathBytes);

if(oleBuilder.getType() == ContentType.SIMPLE_PACKAGE) {
bb.putInt(PS_EMBEDDED_FILE);
bb.putInt(filePathBytes.length);
bb.put(filePathBytes, 0, filePathBytes.length);
bb.putInt((int) oleBuilder.getContentLength());
} else {
bb.putInt(PS_LINKED_FILE);
bb.putShort((short)LINK_HEADER);
}
return headerBytes;
}
private static byte[] writePackageStreamFooter(OleBlob.Builder oleBuilder) {

// note, these are _not_ zero terminated
byte[] fileNameBytes = oleBuilder.getFileName().getBytes(OLE_UTF_CHARSET);
byte[] filePathBytes = oleBuilder.getFilePath().getBytes(OLE_UTF_CHARSET);

int footerLen = 12 + (filePathBytes.length * 2) + fileNameBytes.length;

byte[] footerBytes = new byte[footerLen];
ByteBuffer bb = PageChannel.wrap(footerBytes);

bb.putInt(filePathBytes.length/2);
bb.put(filePathBytes);
bb.putInt(fileNameBytes.length/2);
bb.put(fileNameBytes);
bb.putInt(filePathBytes.length/2);
bb.put(filePathBytes);

return footerBytes;
}
private static ContentImpl createContent(OleBlobImpl blob)
throws IOException
{
ByteBuffer bb = PageChannel.wrap(blob.getBytes());

if((bb.remaining() < 2) || (bb.getShort() != PACKAGE_SIGNATURE)) {
return new UnknownContentImpl(blob);
}

// read outer package header
int headerSize = bb.getShort();
int objType = bb.getInt();
int prettyNameLen = bb.getShort();
int classNameLen = bb.getShort();
int prettyNameOff = bb.getShort();
int classNameOff = bb.getShort();
int objSize = bb.getInt();
String prettyName = readStr(bb, prettyNameOff, prettyNameLen);
String className = readStr(bb, classNameOff, classNameLen);
bb.position(headerSize);

// read ole header
int oleVer = bb.getInt();
int format = bb.getInt();

if(oleVer != OLE_VERSION) {
return new UnknownContentImpl(blob);
}

int typeNameLen = bb.getInt();
String typeName = readStr(bb, bb.position(), typeNameLen);
bb.getLong(); // unused
int dataBlockLen = bb.getInt();
int dataBlockPos = bb.position();


if(SIMPLE_PACKAGE_TYPE.equalsIgnoreCase(typeName)) {
return createSimplePackageContent(
blob, prettyName, className, typeName, bb, dataBlockLen);
}

// if COMPOUND_FACTORY is null, the poi library isn't available, so just
// load compound data as "other"
if((COMPOUND_FACTORY != null) &&
(bb.remaining() >= COMPOUND_STORAGE_SIGNATURE.length) &&
ByteUtil.matchesRange(bb, bb.position(), COMPOUND_STORAGE_SIGNATURE)) {
return COMPOUND_FACTORY.createCompoundPackageContent(
blob, prettyName, className, typeName, bb, dataBlockLen);
}
// this is either some other "special" (as yet unhandled) format, or it is
// simply an embedded file (or it is compound data and poi isn't available)
return new OtherContentImpl(blob, prettyName, className,
typeName, dataBlockPos, dataBlockLen);
}

private static ContentImpl createSimplePackageContent(
OleBlobImpl blob, String prettyName, String className, String typeName,
ByteBuffer blobBb, int dataBlockLen) {

int dataBlockPos = blobBb.position();
ByteBuffer bb = PageChannel.narrowBuffer(blobBb, dataBlockPos,
dataBlockPos + dataBlockLen);
int packageSig = bb.getShort();
if(packageSig != PACKAGE_STREAM_SIGNATURE) {
return new OtherContentImpl(blob, prettyName, className,
typeName, dataBlockPos, dataBlockLen);
}

String fileName = readZeroTermStr(bb);
String filePath = readZeroTermStr(bb);
int packageType = bb.getInt();

if(packageType == PS_EMBEDDED_FILE) {

int localFilePathLen = bb.getInt();
String localFilePath = readStr(bb, bb.position(), localFilePathLen);
int dataLen = bb.getInt();
int dataPos = bb.position();
bb.position(dataLen + dataPos);

// remaining strings are in "reverse" order (local file path, file name,
// file path). these string usee a real utf charset, and therefore can
// "fix" problems with ascii based names (so we prefer these strings to
// the original strings we found)
int strNum = 0;
while(true) {

int rem = bb.remaining();
if(rem < 4) {
break;
}

int strLen = bb.getInt();
String remStr = readStr(bb, bb.position(), strLen * 2, OLE_UTF_CHARSET);

switch(strNum) {
case 0:
localFilePath = remStr;
break;
case 1:
fileName = remStr;
break;
case 2:
filePath = remStr;
break;
default:
// ignore
}

++strNum;
}

return new SimplePackageContentImpl(
blob, prettyName, className, typeName, dataPos, dataLen,
fileName, filePath, localFilePath);
}

if(packageType == PS_LINKED_FILE) {
bb.getShort(); //unknown
String linkStr = readZeroTermStr(bb);

return new LinkContentImpl(blob, prettyName, className, typeName,
fileName, linkStr, filePath);
}

return new OtherContentImpl(blob, prettyName, className,
typeName, dataBlockPos, dataBlockLen);
}

private static String readStr(ByteBuffer bb, int off, int len) {
return readStr(bb, off, len, OLE_CHARSET);
}

private static String readZeroTermStr(ByteBuffer bb) {
int off = bb.position();
while(bb.hasRemaining()) {
byte b = bb.get();
if(b == 0) {
break;
}
}
int len = bb.position() - off;
return readStr(bb, off, len);
}

private static String readStr(ByteBuffer bb, int off, int len,
Charset charset) {
String str = new String(bb.array(), off, len, charset);
bb.position(off + len);
if(str.charAt(str.length() - 1) == '\0') {
str = str.substring(0, str.length() - 1);
}
return str;
}

private static byte[] getZeroTermStrBytes(String str) {
return (str + '\0').getBytes(OLE_CHARSET);
}

static final class OleBlobImpl implements OleBlob
{
private byte[] _bytes;
private ContentImpl _content;

private OleBlobImpl(byte[] bytes) {
_bytes = bytes;
}

public void writeTo(OutputStream out) throws IOException {
out.write(_bytes);
}

public Content getContent() throws IOException {
if(_content == null) {
_content = createContent(this);
}
return _content;
}

public InputStream getBinaryStream() throws SQLException {
return new ByteArrayInputStream(_bytes);
}

public InputStream getBinaryStream(long pos, long len)
throws SQLException
{
return new ByteArrayInputStream(_bytes, fromJdbcOffset(pos), (int)len);
}

public long length() throws SQLException {
return _bytes.length;
}

public byte[] getBytes() throws IOException {
if(_bytes == null) {
throw new IOException("blob is closed");
}
return _bytes;
}

public byte[] getBytes(long pos, int len) throws SQLException {
return ByteUtil.copyOf(_bytes, fromJdbcOffset(pos), len);
}

public long position(byte[] pattern, long start) throws SQLException {
int pos = ByteUtil.findRange(PageChannel.wrap(_bytes),
fromJdbcOffset(start), pattern);
return((pos >= 0) ? toJdbcOffset(pos) : pos);
}
public long position(Blob pattern, long start) throws SQLException {
return position(pattern.getBytes(1L, (int)pattern.length()), start);
}

public OutputStream setBinaryStream(long position) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
public void truncate(long len) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
public int setBytes(long pos, byte[] bytes) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
public int setBytes(long pos, byte[] bytes, int offset, int lesn)
throws SQLException {
throw new SQLFeatureNotSupportedException();
}
public void free() {
close();
}

public void close() {
_bytes = null;
ByteUtil.closeQuietly(_content);
_content = null;
}

private static int toJdbcOffset(int off) {
return off + 1;
}

private static int fromJdbcOffset(long off) {
return (int)off - 1;
}

@Override
public String toString() {
ToStringBuilder sb = CustomToStringStyle.builder(this);
if(_content != null) {
sb.append("content", _content);
} else {
sb.append("bytes", _bytes);
sb.append("content", "(uninitialized)");
}
return sb.toString();
}
}

static abstract class ContentImpl implements Content, Closeable
{
protected final OleBlobImpl _blob;

protected ContentImpl(OleBlobImpl blob) {
_blob = blob;
}

public OleBlobImpl getBlob() {
return _blob;
}

protected byte[] getBytes() throws IOException {
return getBlob().getBytes();
}
public void close() {
// base does nothing
}

protected ToStringBuilder toString(ToStringBuilder sb) {
sb.append("type", getType());
return sb;
}
}

static abstract class EmbeddedContentImpl extends ContentImpl
implements EmbeddedContent
{
private final int _position;
private final int _length;

protected EmbeddedContentImpl(OleBlobImpl blob, int position, int length)
{
super(blob);
_position = position;
_length = length;
}

public long length() {
return _length;
}

public InputStream getStream() throws IOException {
return new ByteArrayInputStream(getBytes(), _position, _length);
}

public void writeTo(OutputStream out) throws IOException {
out.write(getBytes(), _position, _length);
}

@Override
protected ToStringBuilder toString(ToStringBuilder sb) {
super.toString(sb);
if(_position >= 0) {
sb.append("content", ByteBuffer.wrap(_blob._bytes, _position, _length));
}
return sb;
}
}

static abstract class EmbeddedPackageContentImpl
extends EmbeddedContentImpl
implements PackageContent
{
private final String _prettyName;
private final String _className;
private final String _typeName;

protected EmbeddedPackageContentImpl(
OleBlobImpl blob, String prettyName, String className,
String typeName, int position, int length)
{
super(blob, position, length);
_prettyName = prettyName;
_className = className;
_typeName = typeName;
}

public String getPrettyName() {
return _prettyName;
}

public String getClassName() {
return _className;
}

public String getTypeName() {
return _typeName;
}

@Override
protected ToStringBuilder toString(ToStringBuilder sb) {
sb.append("prettyName", _prettyName)
.append("className", _className)
.append("typeName", _typeName);
super.toString(sb);
return sb;
}
}

private static final class LinkContentImpl
extends EmbeddedPackageContentImpl
implements LinkContent
{
private final String _fileName;
private final String _linkPath;
private final String _filePath;

private LinkContentImpl(OleBlobImpl blob, String prettyName,
String className, String typeName,
String fileName, String linkPath,
String filePath)
{
super(blob, prettyName, className, typeName, -1, -1);
_fileName = fileName;
_linkPath = linkPath;
_filePath = filePath;
}

public ContentType getType() {
return ContentType.LINK;
}

public String getFileName() {
return _fileName;
}

public String getLinkPath() {
return _linkPath;
}

public String getFilePath() {
return _filePath;
}

public InputStream getLinkStream() throws IOException {
return new FileInputStream(getLinkPath());
}

@Override
public String toString() {
return toString(CustomToStringStyle.builder(this))
.append("fileName", _fileName)
.append("linkPath", _linkPath)
.append("filePath", _filePath)
.toString();
}
}

private static final class SimplePackageContentImpl
extends EmbeddedPackageContentImpl
implements SimplePackageContent
{
private final String _fileName;
private final String _filePath;
private final String _localFilePath;

private SimplePackageContentImpl(OleBlobImpl blob, String prettyName,
String className, String typeName,
int position, int length,
String fileName, String filePath,
String localFilePath)
{
super(blob, prettyName, className, typeName, position, length);
_fileName = fileName;
_filePath = filePath;
_localFilePath = localFilePath;
}

public ContentType getType() {
return ContentType.SIMPLE_PACKAGE;
}

public String getFileName() {
return _fileName;
}

public String getFilePath() {
return _filePath;
}

public String getLocalFilePath() {
return _localFilePath;
}

@Override
public String toString() {
return toString(CustomToStringStyle.builder(this))
.append("fileName", _fileName)
.append("filePath", _filePath)
.append("localFilePath", _localFilePath)
.toString();
}
}

private static final class OtherContentImpl
extends EmbeddedPackageContentImpl
implements OtherContent
{
private OtherContentImpl(
OleBlobImpl blob, String prettyName, String className,
String typeName, int position, int length)
{
super(blob, prettyName, className, typeName, position, length);
}

public ContentType getType() {
return ContentType.OTHER;
}

@Override
public String toString() {
return toString(CustomToStringStyle.builder(this))
.toString();
}
}

private static final class UnknownContentImpl extends ContentImpl
{
private UnknownContentImpl(OleBlobImpl blob) {
super(blob);
}

public ContentType getType() {
return ContentType.UNKNOWN;
}

@Override
public String toString() {
return toString(CustomToStringStyle.builder(this))
.append("content", _blob._bytes)
.toString();
}
}
}

+ 38
- 22
src/main/java/com/healthmarketscience/jackcess/util/MemFileChannel.java View File

@@ -35,6 +35,7 @@ import java.nio.channels.WritableByteChannel;

import com.healthmarketscience.jackcess.Database;
import com.healthmarketscience.jackcess.DatabaseBuilder;
import com.healthmarketscience.jackcess.impl.ByteUtil;
import com.healthmarketscience.jackcess.impl.DatabaseImpl;

/**
@@ -116,13 +117,7 @@ public class MemFileChannel extends FileChannel
file, DatabaseImpl.RO_CHANNEL_MODE).getChannel(),
mode);
} finally {
if(in != null) {
try {
in.close();
} catch(IOException e) {
// ignore close failure
}
}
ByteUtil.closeQuietly(in);
}
}

@@ -183,15 +178,20 @@ public class MemFileChannel extends FileChannel
@Override
public int read(ByteBuffer dst, long position) throws IOException {
if(position >= _size) {
return -1;
return -1;
}

// we assume reads will always be within a single chunk (due to how mdb
// files work)
byte[] chunk = _data[getChunkIndex(position)];
int chunkOffset = getChunkOffset(position);
int numBytes = dst.remaining();
dst.put(chunk, chunkOffset, numBytes);
int numBytes = (int)Math.min(dst.remaining(), _size - position);
int rem = numBytes;

while(rem > 0) {
byte[] chunk = _data[getChunkIndex(position)];
int chunkOffset = getChunkOffset(position);
int bytesRead = Math.min(rem, CHUNK_SIZE - chunkOffset);
dst.put(chunk, chunkOffset, bytesRead);
rem -= bytesRead;
position += bytesRead;
}

return numBytes;
}
@@ -209,11 +209,16 @@ public class MemFileChannel extends FileChannel
long newSize = position + numBytes;
ensureCapacity(newSize);

// we assume writes will always be within a single chunk (due to how mdb
// files work)
byte[] chunk = _data[getChunkIndex(position)];
int chunkOffset = getChunkOffset(position);
src.get(chunk, chunkOffset, numBytes);
int rem = numBytes;
while(rem > 0) {
byte[] chunk = _data[getChunkIndex(position)];
int chunkOffset = getChunkOffset(position);
int bytesWritten = Math.min(rem, CHUNK_SIZE - chunkOffset);
src.get(chunk, chunkOffset, bytesWritten);
rem -= bytesWritten;
position += bytesWritten;
}

if(newSize > _size) {
_size = newSize;
}
@@ -421,14 +426,25 @@ public class MemFileChannel extends FileChannel
public long write(ByteBuffer[] srcs, int offset, int length)
throws IOException
{
throw new UnsupportedOperationException();
long numBytes = 0L;
for(int i = offset; i < offset + length; ++i) {
numBytes += write(srcs[i]);
}
return numBytes;
}

@Override
public long read(ByteBuffer[] dsts, int offset, int length)
throws IOException
{
throw new UnsupportedOperationException();
{
long numBytes = 0L;
for(int i = offset; i < offset + length; ++i) {
if(_position >= _size) {
return ((numBytes > 0L) ? numBytes : -1L);
}
numBytes += read(dsts[i]);
}
return numBytes;
}

@Override

+ 315
- 0
src/main/java/com/healthmarketscience/jackcess/util/OleBlob.java View File

@@ -0,0 +1,315 @@
/*
Copyright (c) 2013 James Ahlborn

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA
*/

package com.healthmarketscience.jackcess.util;

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Blob;
import java.util.List;

import com.healthmarketscience.jackcess.impl.OleUtil;

/**
*
* Note, implementation is read-only.
*
* @author James Ahlborn
*/
public interface OleBlob extends Blob, Closeable
{
/** Enum describing the types of blob contents which are currently
supported/understood */
public enum ContentType {
/** the blob contents are a link (file path) to some external content.
Content will be an instance LinkContent */
LINK,
/** the blob contents are a simple wrapper around some embedded content
and related file names/paths. Content will be an instance
SimplePackageContent */
SIMPLE_PACKAGE,
/** the blob contents are a complex embedded data known as compound
storage (aka OLE2). Working with compound storage requires the
optional POI library Content will be an instance CompoundContent. If
the POI library is not available on the classpath, then compound
storage data will instead be returned as type {@link #OTHER}. */
COMPOUND_STORAGE,
/** the top-level blob wrapper is understood, but the nested blob contents
are unknown, probably just some embedded content. Content will be an
instance of OtherContent */
OTHER,
/** the top-level blob wrapper is not understood (this may not be a valid
ole instance). Content will simply be an instance of Content (the
data can be accessed from the main blob instance) */
UNKNOWN;
}

/**
* Writes the entire raw blob data to the given stream (this is the access
* db internal format, which includes all wrapper information).
*
* @param out stream to which the blob will be written
*/
public void writeTo(OutputStream out) throws IOException;

/**
* Returns the decoded form of the blob contents, if understandable.
*/
public Content getContent() throws IOException;


public interface Content
{
/**
* Returns the type of this content.
*/
public ContentType getType();

/**
* Returns the blob which owns this content.
*/
public OleBlob getBlob();
}

public interface PackageContent extends Content
{
public String getPrettyName() throws IOException;

public String getClassName() throws IOException;

public String getTypeName() throws IOException;
}

public interface EmbeddedContent extends Content
{
public long length();

public InputStream getStream() throws IOException;

public void writeTo(OutputStream out) throws IOException;
}

public interface LinkContent extends PackageContent
{
public String getFileName();

public String getLinkPath();

public String getFilePath();

public InputStream getLinkStream() throws IOException;
}

public interface SimplePackageContent
extends PackageContent, EmbeddedContent
{
public String getFileName();

public String getFilePath();

public String getLocalFilePath();
}

public interface CompoundContent extends PackageContent, EmbeddedContent
{
public List<String> getEntries() throws IOException;

public InputStream getEntryStream(String entryName) throws IOException;

public boolean hasContentsEntry() throws IOException;

public InputStream getContentsEntryStream() throws IOException;
}

public interface OtherContent extends PackageContent, EmbeddedContent
{
}

/**
* Builder style class for constructing an OleBlob. The {@link
* #fromInternalData} method can be used for interpreting existing ole data.
* <p/>
* Example for creating new ole data:
* <pre>
* OleBlob oleBlob = new OleBlob.Builder()
* .setSimplePackageFile(contentFile)
* .toBlob();
* </pre>
*/
public class Builder
{
public static final String PACKAGE_PRETTY_NAME = "Packager Shell Object";
public static final String PACKAGE_TYPE_NAME = "Package";

private ContentType _type;
private byte[] _bytes;
private InputStream _stream;
private long _contentLen;
private String _fileName;
private String _filePath;
private String _prettyName;
private String _className;
private String _typeName;
public ContentType getType() {
return _type;
}

public byte[] getBytes() {
return _bytes;
}

public InputStream getStream() {
return _stream;
}

public long getContentLength() {
return _contentLen;
}

public String getFileName() {
return _fileName;
}

public String getFilePath() {
return _filePath;
}

public String getPrettyName() {
return _prettyName;
}

public String getClassName() {
return _className;
}
public String getTypeName() {
return _typeName;
}
public Builder setSimplePackageBytes(byte[] bytes) {
_bytes = bytes;
_contentLen = bytes.length;
setDefaultPackageType();
_type = ContentType.SIMPLE_PACKAGE;
return this;
}

public Builder setSimplePackageStream(InputStream in, long length) {
_stream = in;
_contentLen = length;
setDefaultPackageType();
_type = ContentType.SIMPLE_PACKAGE;
return this;
}

public Builder setSimplePackageFileName(String fileName) {
_fileName = fileName;
setDefaultPackageType();
_type = ContentType.SIMPLE_PACKAGE;
return this;
}

public Builder setSimplePackageFilePath(String filePath) {
_filePath = filePath;
setDefaultPackageType();
_type = ContentType.SIMPLE_PACKAGE;
return this;
}

public Builder setSimplePackage(File f) throws FileNotFoundException {
_fileName = f.getName();
_filePath = f.getPath();
return setSimplePackageStream(new FileInputStream(f), f.length());
}

public Builder setLinkFileName(String fileName) {
_fileName = fileName;
setDefaultPackageType();
_type = ContentType.LINK;
return this;
}

public Builder setLinkPath(String link) {
_filePath = link;
setDefaultPackageType();
_type = ContentType.LINK;
return this;
}

public Builder setLink(File f) {
_fileName = f.getName();
_filePath = f.getPath();
setDefaultPackageType();
_type = ContentType.LINK;
return this;
}

private void setDefaultPackageType() {
if(_prettyName == null) {
_prettyName = PACKAGE_PRETTY_NAME;
}
if(_className == null) {
_className = PACKAGE_TYPE_NAME;
}
}

public Builder setOtherBytes(byte[] bytes) {
_bytes = bytes;
_contentLen = bytes.length;
_type = ContentType.OTHER;
return this;
}

public Builder setOtherStream(InputStream in, long length) {
_stream = in;
_contentLen = length;
_type = ContentType.OTHER;
return this;
}

public Builder setPackagePrettyName(String prettyName) {
_prettyName = prettyName;
return this;
}

public Builder setPackageClassName(String className) {
_className = className;
return this;
}

public Builder setPackageTypeName(String typeName) {
_typeName = typeName;
return this;
}

public OleBlob toBlob() throws IOException {
return OleUtil.createBlob(this);
}

public static OleBlob fromInternalData(byte[] bytes) throws IOException {
return OleUtil.parseBlob(bytes);
}
}
}

Loading…
Cancel
Save