Thursday, January 17, 2008

Load XSDs Without Dependencies

Recently my colleague, Jeff Ramsdale, and I were faced with an Eclipse XSD problem: how can we load the contents of an XSD Resource without loading its transitive dependencies?
In our particular situation, we have the following requirements:
  • There are some logical groupings of xsd files.
  • In each grouping there are upwards of 150 schemas, some of which are very large.
  • Many of these schemas refer to one another or refer to schemas that refer to other schemas, etc.
  • We have a TreeViewer that displays these groups and we need to display the schemas in each group and the contents of each schema.
  • We have a custom ItemProviderAdapter for each schema group, and each group's children is a simple object that displays the schema name.
  • For each schema, the getChildren method of the ItemProviderAdapter uses a ResourceSet to demand-load the contents of the XSDResourceImpl to get its contents.
We found that the top level groups were displayed very quickly, but when we tried to open each schema (i.e., load it from the ResourceSet), it could take any number of seconds (10-20 in some cases) to display the schema contents.
Some schemas were causing many transitive dependencies to be loaded into the ResourceSet, even though the contents of those schemas were not displayed in the UI.
The solution, which we implemented thanks to a suggestion by Ed Merks, is to not use the ResourceSet for loading the XSDResourceImpl. We found that we could get the schema contents in much less time and without loading dependent resources just by instantiating the XSDResourceImpl directly with its corresponding URI.
Below I will show you how to set up a test case to demonstrate this solution.

Create a New Eclipse Project


Goto File -> New -> Other -> Plug-in Development -> Plug-in Project and create a project called transitive.dependencies.example.
We will need to declare the following Plug-in dependencies in the MANIFEST.MF
  • org.junit4
  • org.eclipse.emf
  • org.eclipse.emf.ecore
  • org.eclipse.xsd

Plug-in dependencies

Create XSDs With Transitive Dependencies


For this example I created 3 schemas (a.xsd, b.xsd and c.xsd) with transitive dependencies such that a.xsd refers to b.xsd, which refers to c.xsd, which refers to xml.xsd.
The three schemas are below and can be dropped into the folder /transitive.dependencies.example/xsd in our workspace.

a.xsd

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://transitive.dependencies.example/a"
xmlns:b="http://transitive.dependencies.example/b"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://transitive.dependencies.example/a">
<xsd:annotation>
<xsd:documentation>schema a ($Date: 2008/01/16 00:14:59 $) </xsd:documentation>
</xsd:annotation>
<xsd:import namespace="http://transitive.dependencies.example/b"
schemaLocation="xsd/b.xsd"/>
<xsd:attributeGroup name="a.attributes">
<xsd:attribute ref="b:b.type"/>
</xsd:attributeGroup>
</xsd:schema>


b.xsd

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://transitive.dependencies.example/b"
xmlns:c="http://transitive.dependencies.example/c"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://transitive.dependencies.example/b">
<xsd:annotation>
<xsd:documentation>schema b ($Date: 2008/01/16 00:14:59 $) </xsd:documentation>
</xsd:annotation>
<xsd:import namespace="http://transitive.dependencies.example/c"
schemaLocation="xsd/c.xsd"/>
<xsd:attribute name="b.type" type="xsd:anyURI"/>
<xsd:attributeGroup name="b.attributes">
<xsd:attribute ref="c:c.type"/>
</xsd:attributeGroup>
</xsd:schema>


c.xsd

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xsd:schema xmlns="http://transitive.dependencies.example/c"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://transitive.dependencies.example/c">
<xsd:annotation>
<xsd:documentation>schema c ($Date: 2008/01/16 00:14:59 $) </xsd:documentation>
</xsd:annotation>
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"
schemaLocation="http://www.w3.org/2001/xml.xsd"/>
<xsd:attribute name="c.type" type="xsd:anyURI"/>
<xsd:attributeGroup name="c.attributes">
<xsd:attribute ref="xml:id"/>
<xsd:attribute ref="xml:lang"/>
</xsd:attributeGroup>
</xsd:schema>


Create a New Test Case


For our first test, we want to show that when we demand-load an XSDResourceImpl through a ResourceSet, all of its transitive dependencies are also loaded into the ResourceSet.


package transitive.dependencies.example;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.xsd.impl.XSDSchemaImpl;
import org.eclipse.xsd.util.XSDResourceImpl;
import org.junit.Test;

public class LoadXsdTest {

@Test
public void demandLoadFromResourceSet() throws Exception {
final ResourceSet resourceSet = XSDSchemaImpl.createResourceSet();
assertNotNull(resourceSet.getResource(URI.createFileURI("xsd/a.xsd"),
true));
assertEquals(4, resourceSet.getResources().size());
assertNotNull(resourceSet.getResource(URI.createFileURI("xsd/b.xsd"),
false));
assertNotNull(resourceSet.getResource(URI.createFileURI("xsd/c.xsd"),
false));
assertNotNull(resourceSet.getResource(
URI.createURI("http://www.w3.org/2001/xml.xsd"), false));
}
}



For our next test, we want to show that when we load an orphaned XSDResourceImpl and manually put it into and retrieve it from the ResourceSet, that its dependencies are not automatically resolved.


....
@Test
public void createAndAddDetachedResources() throws Exception {
final ResourceSet resourceSet = XSDSchemaImpl.createResourceSet();
final URI uri = URI.createFileURI("xsd/a.xsd");
resourceSet.getResources().add(new XSDResourceImpl(uri));
assertEquals(1, resourceSet.getResources().size());

assertNotNull(resourceSet.getResource(uri, false));
assertEquals(1, resourceSet.getResources().size());
}
....


Interestingly, in the second test case, if we set the demandLoad flag to true when we get the XSDResourceImpl from the ResourceSet, the ResourceSet will load all the dependent resources, even though a.xsd has already been loaded and is present in the ResourceSet.  In that case, the second assertion will fail.

Both of the above test cases should pass as written.  Run them to get a green bar!

No comments: