Bug 60226 - ClassLoader workaround for OSGI when processing OOXML files

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1763922 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2016-10-08 17:07:15 +00:00
parent 9b908a1994
commit cb03495d36
3 changed files with 103 additions and 34 deletions

View File

@ -23,6 +23,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.Reader; import java.io.Reader;
import java.io.StringReader; import java.io.StringReader;
import java.lang.ref.WeakReference;
import java.net.URL; import java.net.URL;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -32,6 +33,7 @@ import javax.xml.stream.XMLStreamReader;
import org.apache.poi.util.DocumentHelper; import org.apache.poi.util.DocumentHelper;
import org.apache.xmlbeans.SchemaType; import org.apache.xmlbeans.SchemaType;
import org.apache.xmlbeans.SchemaTypeLoader;
import org.apache.xmlbeans.XmlBeans; import org.apache.xmlbeans.XmlBeans;
import org.apache.xmlbeans.XmlException; import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject; import org.apache.xmlbeans.XmlObject;
@ -46,6 +48,8 @@ import org.xml.sax.SAXException;
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public class POIXMLTypeLoader { public class POIXMLTypeLoader {
private static ThreadLocal<ClassLoader> classLoader = new ThreadLocal<ClassLoader>();
public static final XmlOptions DEFAULT_XML_OPTIONS; public static final XmlOptions DEFAULT_XML_OPTIONS;
static { static {
DEFAULT_XML_OPTIONS = new XmlOptions(); DEFAULT_XML_OPTIONS = new XmlOptions();
@ -80,8 +84,32 @@ public class POIXMLTypeLoader {
return options == null ? DEFAULT_XML_OPTIONS : options; return options == null ? DEFAULT_XML_OPTIONS : options;
} }
/**
* Sets the {@link ClassLoader} which is used, when XmlBeans are dynamically instantiated -
* opposed to being loaded by the factory class which is accompanied by each generated XmlBeans interface.
* <p>
* This is especially necessary in a context which doesn't guarantee that the current (thread) context
* cassloader has access to all XmlBeans schema definitions (*.xsb) - which is typically in OSGI the case.
* <p>
* The classloader will be only set for the current thread in a {@link ThreadLocal}. Although the
* ThreadLocal is implemented via a {@link WeakReference}, it's good style to {@code null} the classloader
* when the user code is finalized.
*
* @param cl the classloader to be used when XmlBeans classes and definitions are looked up
*/
public static void setClassLoader(ClassLoader cl) {
classLoader.set(cl);
}
private static SchemaTypeLoader getTypeLoader() {
ClassLoader cl = classLoader.get();
return (cl == null)
? XmlBeans.getContextTypeLoader()
: XmlBeans.typeLoaderForClassLoader(cl);
}
public static XmlObject newInstance(SchemaType type, XmlOptions options) { public static XmlObject newInstance(SchemaType type, XmlOptions options) {
return XmlBeans.getContextTypeLoader().newInstance(type, getXmlOptions(options)); return getTypeLoader().newInstance(type, getXmlOptions(options));
} }
public static XmlObject parse(String xmlText, SchemaType type, XmlOptions options) throws XmlException { public static XmlObject parse(String xmlText, SchemaType type, XmlOptions options) throws XmlException {
@ -113,34 +141,34 @@ public class POIXMLTypeLoader {
public static XmlObject parse(InputStream jiois, SchemaType type, XmlOptions options) throws XmlException, IOException { public static XmlObject parse(InputStream jiois, SchemaType type, XmlOptions options) throws XmlException, IOException {
try { try {
Document doc = DocumentHelper.readDocument(jiois); Document doc = DocumentHelper.readDocument(jiois);
return XmlBeans.getContextTypeLoader().parse(doc.getDocumentElement(), type, getXmlOptions(options)); return getTypeLoader().parse(doc.getDocumentElement(), type, getXmlOptions(options));
} catch (SAXException e) { } catch (SAXException e) {
throw new IOException("Unable to parse xml bean", e); throw new IOException("Unable to parse xml bean", e);
} }
} }
public static XmlObject parse(XMLStreamReader xsr, SchemaType type, XmlOptions options) throws XmlException { public static XmlObject parse(XMLStreamReader xsr, SchemaType type, XmlOptions options) throws XmlException {
return XmlBeans.getContextTypeLoader().parse(xsr, type, getXmlOptions(options)); return getTypeLoader().parse(xsr, type, getXmlOptions(options));
} }
public static XmlObject parse(Reader jior, SchemaType type, XmlOptions options) throws XmlException, IOException { public static XmlObject parse(Reader jior, SchemaType type, XmlOptions options) throws XmlException, IOException {
try { try {
Document doc = DocumentHelper.readDocument(new InputSource(jior)); Document doc = DocumentHelper.readDocument(new InputSource(jior));
return XmlBeans.getContextTypeLoader().parse(doc.getDocumentElement(), type, getXmlOptions(options)); return getTypeLoader().parse(doc.getDocumentElement(), type, getXmlOptions(options));
} catch (SAXException e) { } catch (SAXException e) {
throw new XmlException("Unable to parse xml bean", e); throw new XmlException("Unable to parse xml bean", e);
} }
} }
public static XmlObject parse(Node node, SchemaType type, XmlOptions options) throws XmlException { public static XmlObject parse(Node node, SchemaType type, XmlOptions options) throws XmlException {
return XmlBeans.getContextTypeLoader().parse(node, type, getXmlOptions(options)); return getTypeLoader().parse(node, type, getXmlOptions(options));
} }
public static XmlObject parse(XMLInputStream xis, SchemaType type, XmlOptions options) throws XmlException, XMLStreamException { public static XmlObject parse(XMLInputStream xis, SchemaType type, XmlOptions options) throws XmlException, XMLStreamException {
return XmlBeans.getContextTypeLoader().parse(xis, type, getXmlOptions(options)); return getTypeLoader().parse(xis, type, getXmlOptions(options));
} }
public static XMLInputStream newValidatingXMLInputStream ( XMLInputStream xis, SchemaType type, XmlOptions options ) throws XmlException, XMLStreamException { public static XMLInputStream newValidatingXMLInputStream ( XMLInputStream xis, SchemaType type, XmlOptions options ) throws XmlException, XMLStreamException {
return XmlBeans.getContextTypeLoader().newValidatingXMLInputStream(xis, type, getXmlOptions(options)); return getTypeLoader().newValidatingXMLInputStream(xis, type, getXmlOptions(options));
} }
} }

View File

@ -19,8 +19,6 @@
package org.apache.poi.xslf.usermodel; package org.apache.poi.xslf.usermodel;
import static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -29,7 +27,6 @@ import java.util.List;
import javax.xml.namespace.QName; import javax.xml.namespace.QName;
import org.apache.poi.POIXMLException;
import org.apache.poi.sl.draw.DrawFactory; import org.apache.poi.sl.draw.DrawFactory;
import org.apache.poi.sl.draw.DrawTableShape; import org.apache.poi.sl.draw.DrawTableShape;
import org.apache.poi.sl.draw.DrawTextShape; import org.apache.poi.sl.draw.DrawTextShape;
@ -37,7 +34,6 @@ import org.apache.poi.sl.usermodel.TableShape;
import org.apache.poi.util.Internal; import org.apache.poi.util.Internal;
import org.apache.poi.util.Units; import org.apache.poi.util.Units;
import org.apache.xmlbeans.XmlCursor; import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject; import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.impl.values.XmlAnyTypeImpl; import org.apache.xmlbeans.impl.values.XmlAnyTypeImpl;
import org.openxmlformats.schemas.drawingml.x2006.main.CTGraphicalObjectData; import org.openxmlformats.schemas.drawingml.x2006.main.CTGraphicalObjectData;
@ -53,6 +49,7 @@ import org.openxmlformats.schemas.presentationml.x2006.main.CTGraphicalObjectFra
public class XSLFTable extends XSLFGraphicFrame implements Iterable<XSLFTableRow>, public class XSLFTable extends XSLFGraphicFrame implements Iterable<XSLFTableRow>,
TableShape<XSLFShape,XSLFTextParagraph> { TableShape<XSLFShape,XSLFTextParagraph> {
/* package */ static final String TABLE_URI = "http://schemas.openxmlformats.org/drawingml/2006/table"; /* package */ static final String TABLE_URI = "http://schemas.openxmlformats.org/drawingml/2006/table";
/* package */ static final String DRAWINGML_URI = "http://schemas.openxmlformats.org/drawingml/2006/main";
private CTTable _table; private CTTable _table;
private List<XSLFTableRow> _rows; private List<XSLFTableRow> _rows;
@ -60,28 +57,30 @@ public class XSLFTable extends XSLFGraphicFrame implements Iterable<XSLFTableRow
/*package*/ XSLFTable(CTGraphicalObjectFrame shape, XSLFSheet sheet){ /*package*/ XSLFTable(CTGraphicalObjectFrame shape, XSLFSheet sheet){
super(shape, sheet); super(shape, sheet);
XmlObject[] rs = shape.getGraphic().getGraphicData() CTGraphicalObjectData god = shape.getGraphic().getGraphicData();
.selectPath("declare namespace a='http://schemas.openxmlformats.org/drawingml/2006/main' ./a:tbl"); XmlCursor xc = god.newCursor();
if (rs.length == 0) { if (!xc.toChild(DRAWINGML_URI, "tbl")) {
throw new IllegalStateException("a:tbl element was not found in\n " + shape.getGraphic().getGraphicData()); throw new IllegalStateException("a:tbl element was not found in\n " + god);
} }
XmlObject xo = xc.getObject();
// Pesky XmlBeans bug - see Bugzilla #49934 // Pesky XmlBeans bug - see Bugzilla #49934
// it never happens when using the full ooxml-schemas jar but may happen with the abridged poi-ooxml-schemas // it never happens when using the full ooxml-schemas jar but may happen with the abridged poi-ooxml-schemas
if(rs[0] instanceof XmlAnyTypeImpl){ if (xo instanceof XmlAnyTypeImpl){
try { String errStr =
rs[0] = CTTable.Factory.parse(rs[0].toString(), DEFAULT_XML_OPTIONS); "Schemas (*.xsb) for CTTable can't be loaded - usually this happens when OSGI " +
}catch (XmlException e){ "loading is used and the thread context classloader has no reference to " +
throw new POIXMLException(e); "the xmlbeans classes - use POIXMLTypeLoader.setClassLoader() to set the loader, " +
} "e.g. with CTTable.class.getClassLoader()"
;
throw new IllegalStateException(errStr);
} }
_table = (CTTable)xo;
xc.dispose();
_table = (CTTable) rs[0]; _rows = new ArrayList<XSLFTableRow>(_table.sizeOfTrArray());
CTTableRow[] trArray = _table.getTrArray(); for(CTTableRow row : _table.getTrArray()) {
_rows = new ArrayList<XSLFTableRow>(trArray.length); _rows.add(new XSLFTableRow(row, this));
for(CTTableRow row : trArray) {
XSLFTableRow xr = new XSLFTableRow(row, this);
_rows.add(xr);
} }
updateRowColIndexes(); updateRowColIndexes();
} }
@ -171,13 +170,18 @@ public class XSLFTable extends XSLFGraphicFrame implements Iterable<XSLFTableRow
frame.addNewXfrm(); frame.addNewXfrm();
CTGraphicalObjectData gr = frame.addNewGraphic().addNewGraphicData(); CTGraphicalObjectData gr = frame.addNewGraphic().addNewGraphicData();
XmlCursor cursor = gr.newCursor(); XmlCursor grCur = gr.newCursor();
cursor.toNextToken(); grCur.toNextToken();
cursor.beginElement(new QName("http://schemas.openxmlformats.org/drawingml/2006/main", "tbl")); grCur.beginElement(new QName(DRAWINGML_URI, "tbl"));
cursor.beginElement(new QName("http://schemas.openxmlformats.org/drawingml/2006/main", "tblPr"));
cursor.toNextToken(); CTTable tbl = CTTable.Factory.newInstance();
cursor.beginElement(new QName("http://schemas.openxmlformats.org/drawingml/2006/main", "tblGrid")); tbl.addNewTblPr();
cursor.dispose(); tbl.addNewTblGrid();
XmlCursor tblCur = tbl.newCursor();
tblCur.moveXmlContents(grCur);
tblCur.dispose();
grCur.dispose();
gr.setUri(TABLE_URI); gr.setUri(TABLE_URI);
return frame; return frame;
} }

View File

@ -27,6 +27,7 @@ import static org.junit.Assert.fail;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@ -41,6 +42,8 @@ import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;
import org.apache.poi.util.NullOutputStream; import org.apache.poi.util.NullOutputStream;
import org.apache.poi.util.PackageHelper; import org.apache.poi.util.PackageHelper;
import org.apache.poi.util.TempFile; import org.apache.poi.util.TempFile;
import org.apache.poi.xslf.usermodel.XMLSlideShow;
import org.apache.poi.xslf.usermodel.XSLFShape;
import org.junit.Test; import org.junit.Test;
/** /**
@ -277,4 +280,38 @@ public final class TestPOIXMLDocument {
open.close(); open.close();
} }
} }
@Test(expected=IllegalStateException.class)
public void testOSGIClassLoadingAsIs() throws IOException {
Thread thread = Thread.currentThread();
ClassLoader cl = thread.getContextClassLoader();
InputStream is = POIDataSamples.getSlideShowInstance().openResourceAsStream("table_test.pptx");
try {
thread.setContextClassLoader(cl.getParent());
XMLSlideShow ppt = new XMLSlideShow(is);
ppt.getSlides().get(0).getShapes();
} finally {
thread.setContextClassLoader(cl);
is.close();
}
}
@Test
public void testOSGIClassLoadingFixed() throws IOException {
Thread thread = Thread.currentThread();
ClassLoader cl = thread.getContextClassLoader();
InputStream is = POIDataSamples.getSlideShowInstance().openResourceAsStream("table_test.pptx");
try {
thread.setContextClassLoader(cl.getParent());
POIXMLTypeLoader.setClassLoader(cl);
XMLSlideShow ppt = new XMLSlideShow(is);
ppt.getSlides().get(0).getShapes();
} finally {
thread.setContextClassLoader(cl);
POIXMLTypeLoader.setClassLoader(null);
is.close();
}
}
} }