blob: 0483542b7836a8d7b0cbf9c4ff892e2e321e0ce2 [file] [log] [blame]
/* Copyright 2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.xmlbeans.impl.binding.tylar;
import java.io.*;
import java.net.URI;
import java.net.URLClassLoader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import org.apache.xml.xmlbeans.bindingConfig.BindingConfigDocument;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.SchemaTypeSystem;
import org.apache.xmlbeans.impl.binding.bts.BindingFile;
import org.apache.xmlbeans.impl.schema.SchemaTypeSystemImpl;
import org.w3.x2001.xmlSchema.SchemaDocument;
/**
* Default implementation of TylarLoader. Currently, only directory and jar
* tylars are supported.
*
* @author Patrick Calahan <pcal@bea.com>
*/
public class DefaultTylarLoader implements TylarLoader, TylarConstants {
// ========================================================================
// Singleton
//REVIEW someday we might want to make the default TylarLoader a pluggable
//parameter in a properties file somewhere. pcal 12/16/03
public static final TylarLoader getInstance() {
return DEFAULT_INSTANCE;
}
private static /*final*/ TylarLoader
DEFAULT_INSTANCE = new DefaultTylarLoader();
/**
* This is a gross quick hack to support pluggability for tylar loader.
* In the future, we need a cleaner mechansim, probably letting them
* specifiy the TylarLoader impl class name in some properties file
* in the classpath.
*
* @deprecated eventually; currently there is no other option.
*/
public static void setInstance(TylarLoader newDefaultLoader) {
DEFAULT_INSTANCE = newDefaultLoader;
}
// ========================================================================
// Constructor
protected DefaultTylarLoader() {}
// ========================================================================
// Public methods
/**
*
*/
public Tylar load(ClassLoader cl) throws IOException, XmlException {
if (cl == null) throw new IllegalArgumentException("null stream");
return new RuntimeTylar(cl);
}
// ========================================================================
// Everything below this line is deprecated and will be removed ASAP
private static final String FILE_SCHEME = "file";
private static final char[] OTHER_SEPCHARS = {'\\'};
private static final char SEPCHAR = '/';
private static final boolean VERBOSE = false;
private static final String BINDING_FILE_JARENTRY =
normalizeEntryName(TylarConstants.BINDING_FILE).toLowerCase();
private static final String SCHEMA_DIR_JARENTRY =
normalizeEntryName(TylarConstants.SCHEMA_DIR).toLowerCase();
private static final String SCHEMA_EXT = ".xsd";
private static final String STS_PREFIX = "schema"+SEPCHAR+"system"+SEPCHAR;
/**
* Loads the tylar from the given uri.
*
* @param uri uri of where the tylar is stored.
* @return
* @throws IOException if an i/o error occurs while processing
* @throws XmlException if an error occurs parsing the contents of the tylar.
*/
/*
public Tylar load(URI uri) throws IOException, XmlException
{
return load(new URL[]{new URL(uri.toString())});
}
public Tylar load(URI[] uris) throws IOException, XmlException {
URL[] urls = new URL[uris.length];
for(int i=0; i<uris.length; i++) {
urls[i] = new URL(uris[i].toString());
}
return load(urls);
}
public Tylar load(JarInputStream jar) throws IOException, XmlException {
if (jar == null) throw new IllegalArgumentException("null stream");
return loadFromJar(jar,null);
} */
/**
* Loads the tylar from the given uri.
*
* @param uri uri of where the tylar is stored.
* @return
* @throws IOException if an i/o error occurs while processing
* @throws XmlException if an error occurs parsing the contents of the tylar.
*/
public Tylar load(URI uri) throws IOException, XmlException
{
if (uri == null) throw new IllegalArgumentException("null uri");
//String scheme = uri.getScheme();
File file = null;
try {
file = new File(uri);
} catch(Exception ignore) {}
if (file != null && file.exists() && file.isDirectory()) {
return ExplodedTylarImpl.load(file);
} else {
return loadFromJar(new JarInputStream(uri.toURL().openStream()),uri);
}
}
public Tylar load(URI[] uris) throws IOException, XmlException {
Tylar[] tylars = new Tylar[uris.length];
for(int i=0; i<tylars.length; i++) {
tylars[i] = load(uris[i]);
}
return new CompositeTylar(tylars);
}
public Tylar load(JarInputStream jar) throws IOException, XmlException {
if (jar == null) throw new IllegalArgumentException("null stream");
return loadFromJar(jar,null);
}
// ========================================================================
// Private methods
/**
* Loads a Tylar directly from the stream. given jar file. This method
* parses all of the tylar's binding artifacts; if it doesn't throw an
* exception, you can be sure that the tylars binding files and schemas are
* valid.
*
* @param jin input stream on the jar file. This should NOT be a
* JarInputStream
* @param source uri from which the tylar was retrieved. This is used
* for informational purposes only and is not required.
* @return Handle to the tylar
* @throws IOException
*/
protected static Tylar loadFromJar(JarInputStream jin, URI source)
throws IOException, XmlException
{
if (jin == null) throw new IllegalArgumentException("null stream");
//FIXME in the case where sourceURI is null, we could look in the
//manifest or someplace to try to get at least some useful information
JarEntry entry;
BindingFile bf = null;
Collection schemas = null;
StubbornInputStream stubborn = new StubbornInputStream(jin);
String stsName = null;
while ((entry = jin.getNextJarEntry()) != null) {
String name = normalizeEntryName(entry.getName());
if (name.endsWith(""+SEPCHAR)) {
if (name.startsWith(STS_PREFIX) &&
name.length() > STS_PREFIX.length()) {
// the name of the sts is the name of the only directory under
// schema/system
stsName = STS_PACKAGE+"."+name.substring(STS_PREFIX.length(),name.length()-1);
if (VERBOSE) System.out.println("sts name is "+stsName);
}
continue;
}
name = name.toLowerCase();
if (name.equals(BINDING_FILE_JARENTRY)) {
if (VERBOSE) System.out.println("parsing binding file "+name);
bf = BindingFile.forDoc(BindingConfigDocument.Factory.parse(stubborn));
} else if (name.startsWith(SCHEMA_DIR_JARENTRY) &&
name.endsWith(SCHEMA_EXT)) {
if (schemas == null) schemas = new ArrayList();
if (VERBOSE) System.out.println("parsing schema "+name);
schemas.add(SchemaDocument.Factory.parse(stubborn));
} else {
if (VERBOSE) {
System.out.println("ignoring unknown jar entry: "+name);
System.out.println(" looking for "+BINDING_FILE_JARENTRY+" or "+
SCHEMA_DIR_JARENTRY);
}
}
jin.closeEntry();
}
if (VERBOSE) System.out.println("Done reading jar entries");
if (bf == null) {
throw new IOException
("resource at '"+source+
"' is not a tylar: it does not contain a binding file");
}
jin.close();
if (VERBOSE) System.out.println("Done reading jar entries");
SchemaTypeSystem sts = null;
if (stsName != null && source != null) {
{
try {
URLClassLoader ucl = new URLClassLoader(new URL[] {source.toURL()});
sts = SchemaTypeSystemImpl.forName(stsName,ucl);
if (sts == null) throw new IllegalStateException("null returned by SchemaTypeSystemImpl.forName()");
if (VERBOSE) System.out.println("successfully loaded schema type system");
} catch(Exception e) {
ExplodedTylarImpl.showXsbError(e,source,"read",TylarConstants.SHOW_XSB_ERRORS);
}
}
}
return new TylarImpl((source == null) ? null : new URL[]{source.toURL()},
bf,schemas,sts);
}
// ========================================================================
// Private methods
/**
* Canonicalizes the given zip entry path so that we can look for what
* we want without having to worry about different slashes or
* leading slashes or anything else that can go wrong.
*/
private static final String normalizeEntryName(String name) {
name = name.trim();
for(int i=0; i<OTHER_SEPCHARS.length; i++) {
name = name.replace(OTHER_SEPCHARS[i],SEPCHAR);
}
if (name.charAt(0) == SEPCHAR) name = name.substring(1);
return name;
}
/**
* This is another hack around what I believe is an xbeans bug - it
* closes the stream on us. When we're reading out of a jar, we want
* to parse a whole bunch of files from the same stream - this class
* just intercepts the close() call and ignores it until we call
* reallyClose().
*/
private static class StubbornInputStream extends FilterInputStream {
StubbornInputStream(InputStream in) { super(in); }
public void close() {}
public void reallyClose() throws IOException {
super.close();
}
}
// ========================================================================
// Private methods
/**
* Loads a Tylar directly from the stream. given jar file. This method
* parses all of the tylar's binding artifacts; if it doesn't throw an
* exception, you can be sure that the tylars binding files and schemas are
* valid.
*
* @param jin input stream on the jar file. This should NOT be a
* JarInputStream
* @param source uri from which the tylar was retrieved. This is used
* for informational purposes only and is not required.
* @return Handle to the tylar
* @throws IOException
protected static Tylar loadFromJar(JarInputStream jin, URI source)
throws IOException, XmlException
{
if (jin == null) throw new IllegalArgumentException("null stream");
//FIXME in the case where sourceURI is null, we could look in the
//manifest or someplace to try to get at least some useful information
JarEntry entry;
BindingFile bf = null;
Collection schemas = null;
StubbornInputStream stubborn = new StubbornInputStream(jin);
String stsName = null;
while ((entry = jin.getNextJarEntry()) != null) {
String name = normalizeEntryName(entry.getName());
if (name.endsWith(""+SEPCHAR)) {
if (name.startsWith(STS_PREFIX) &&
name.length() > STS_PREFIX.length()) {
// the name of the sts is the name of the only directory under
// schema/system
stsName = STS_PACKAGE+"."+name.substring(STS_PREFIX.length(),name.length()-1);
if (VERBOSE) System.out.println("sts name is "+stsName);
}
continue;
}
name = name.toLowerCase();
if (name.equals(BINDING_FILE_JARENTRY)) {
if (VERBOSE) System.out.println("parsing binding file "+name);
bf = BindingFile.forDoc(BindingConfigDocument.Factory.parse(stubborn));
} else if (name.startsWith(SCHEMA_DIR_JARENTRY) &&
name.endsWith(SCHEMA_EXT)) {
if (schemas == null) schemas = new ArrayList();
if (VERBOSE) System.out.println("parsing schema "+name);
schemas.add(SchemaDocument.Factory.parse(stubborn));
} else {
if (VERBOSE) {
System.out.println("ignoring unknown jar entry: "+name);
System.out.println(" looking for "+BINDING_FILE_JARENTRY+" or "+
SCHEMA_DIR_JARENTRY);
}
}
jin.closeEntry();
}
if (VERBOSE) System.out.println("Done reading jar entries");
if (bf == null) {
throw new IOException
("resource at '"+source+
"' is not a tylar: it does not contain a binding file");
}
jin.close();
if (VERBOSE) System.out.println("Done reading jar entries");
SchemaTypeSystem sts = null;
if (stsName != null && source != null) {
{
try {
URLClassLoader ucl = new URLClassLoader(new URL[] {source.toURL()});
sts = SchemaTypeSystemImpl.forName(stsName,ucl);
if (sts == null) throw new IllegalStateException("null returned by SchemaTypeSystemImpl.forName()");
if (VERBOSE) System.out.println("successfully loaded schema type system");
} catch(Exception e) {
ExplodedTylarImpl.showXsbError(e,source,"read",TylarConstants.SHOW_XSB_ERRORS);
}
}
}
return new TylarImpl(source,bf,schemas,sts);
}
*/
// ========================================================================
// Private methods
/*
private static Tylar load(ClassLoader loader,
String stsName,
String[] xsds,
URI source)
throws XmlException, IOException
{
SchemaTypeSystem sts = null;
BindingFile bf = null;
{
InputStream in = loader.getResourceAsStream(BINDING_FILE_JARENTRY);
bf = BindingFile.forDoc(BindingConfigDocument.Factory.parse(in));
}
if (stsName != null) {
try {
sts = SchemaTypeSystemImpl.forName(stsName,loader);
} catch(Exception e) {
ExplodedTylarImpl.showXsbError(e,source,"read",TylarConstants.SHOW_XSB_ERRORS);
}
}
if (sts == null) {
}
}
*/
/**
* Canonicalizes the given zip entry path so that we can look for what
* we want without having to worry about different slashes or
* leading slashes or anything else that can go wrong.
private static final String normalizeEntryName(String name) {
name = name.trim();
for(int i=0; i<OTHER_SEPCHARS.length; i++) {
name = name.replace(OTHER_SEPCHARS[i],SEPCHAR);
}
if (name.charAt(0) == SEPCHAR) name = name.substring(1);
return name;
}
*/
/**
* This is another hack around what I believe is an xbeans bug - it
* closes the stream on us. When we're reading out of a jar, we want
* to parse a whole bunch of files from the same stream - this class
* just intercepts the close() call and ignores it until we call
* reallyClose().
private static class StubbornInputStream extends FilterInputStream {
StubbornInputStream(InputStream in) { super(in); }
public void close() {}
public void reallyClose() throws IOException {
super.close();
}
}
*/
/**
* Grab the contents of the current entry and stuffs them into a string -
* sometimes useful for debugging.
*/
/*
private static String getEntryContents(JarInputStream in) throws IOException {
StringWriter output = new StringWriter();
byte[] buffer = new byte[2056];
int count = 0;
while ((count = in.read(buffer, 0, buffer.length)) != -1) {
output.write(new String(buffer, 0, count));
}
if (VERBOSE) {
System.out.println("=== ENTRY CONTENTS ===");
System.out.println(output.toString());
System.out.println("=== ENTRY CONTENTS ===");
}
return output.toString();
}
*/
}