// $Header: /home/deegree/jail/deegreerepository/deegree/src/org/deegree/framework/xml/XMLFragment.java,v 1.56 2006/10/31 16:51:42 mschneider Exp $
/*---------------- FILE HEADER ------------------------------------------
This file is part of deegree.
Copyright (C) 2001-2006 by:
EXSE, Department of Geography, University of Bonn
http://www.giub.uni-bonn.de/deegree/
lat/lon GmbH
http://www.lat-lon.de
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
Contact:
Andreas Poth
lat/lon GmbH
Aennchenstr. 19
53115 Bonn
Germany
E-Mail: poth@lat-lon.de
Prof. Dr. Klaus Greve
Department of Geography
University of Bonn
Meckenheimer Allee 166
53115 Bonn
Germany
E-Mail: greve@giub.uni-bonn.de
---------------------------------------------------------------------------*/
package org.deegree.framework.xml;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Serializable;
import java.io.StringWriter;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.StringTokenizer;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.deegree.datatypes.QualifiedName;
import org.deegree.datatypes.xlink.SimpleLink;
import org.deegree.framework.log.ILogger;
import org.deegree.framework.log.LoggerFactory;
import org.deegree.framework.util.BootLogger;
import org.deegree.framework.util.CharsetUtils;
import org.deegree.framework.util.StringTools;
import org.deegree.model.feature.Messages;
import org.deegree.ogcbase.CommonNamespaces;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* An instance of <code>XMLFragment</code> encapsulates an underlying {@link Element} which
* acts as the root element of the document (which may be a fragment or a whole document).
* <p>
* Basically, <code>XMLFragment</code> provides easy loading and proper saving (automatically
* generated CDATA-elements for text nodes that need to be escaped) and acts as base class for all
* XML parsers in deegree.
*
* TODO: automatically generated CDATA-elements are not implemented yet
*
* <p>
* Additionally, <code>XMLFragment</code> tries to make the handling of relative paths inside the
* document's content as painless as possible. This means that after initialization of the
* <code>XMLFragment</code> with the correct SystemID (i.e. the URL of the document):
* <ul>
* <li>external parsed entities (in the DOCTYPE part) can use relative URLs; e.g. <!ENTITY local
* SYSTEM "conf/wfs/wfs.cfg"></li>
* <li>application specific documents which extend <code>XMLFragment</code> can resolve relative
* URLs during parsing by calling the <code>resolve()</code> method</li>
* </ul>
*
* @author <a href="mailto:tfr@users.sourceforge.net">Torsten Friebe </a>
* @author <a href="mailto:schneider@lat-lon.de">Markus Schneider </a>
* @author last edited by: $Author: mschneider $
*
* @version $Revision: 1.56 $, $Date: 2006/10/31 16:51:42 $
*
* @see org.deegree.framework.xml.XMLTools
*/
public class XMLFragment implements Serializable {
private static final long serialVersionUID = 8984447437613709386L;
protected static NamespaceContext nsContext = CommonNamespaces.getNamespaceContext();
protected static final URI XLNNS = CommonNamespaces.XLNNS;
protected static final ILogger LOG = LoggerFactory.getLogger( XMLFragment.class );
/**
* Use this URL as SystemID only if an <code>XMLFragment</code> cannot be pinpointed to a URL -
* in this case it may not use any relative references!
*/
public static final String DEFAULT_URL = "http://www.deegree.org";
private URL systemId;
private Element rootElement;
private static final String PRETTY_PRINTER_RESOURCE = "PrettyPrinter.xsl";
private static XSLTDocument PRETTY_PRINTER_XSLT = new XSLTDocument();
static {
LOG.logDebug( "DOM implementation in use (DocumentBuilderFactory): "
+ DocumentBuilderFactory.newInstance().getClass().getName() );
try {
LOG.logDebug( "DOM implementation in use (DocumentBuilder): "
+ DocumentBuilderFactory.newInstance().newDocumentBuilder().getClass().getName() );
} catch ( Exception e ) {
BootLogger.logError( "Error creating test DocumentBuilder instance.", e );
}
try {
URL url = XMLFragment.class.getResource( PRETTY_PRINTER_RESOURCE );
if ( url == null ) {
throw new IOException( "The resource '" + PRETTY_PRINTER_RESOURCE
+ " could not be found." );
}
PRETTY_PRINTER_XSLT.load( url );
} catch ( Exception e ) {
BootLogger.logError( "Error loading PrettyPrinter-XSLT document: " + e.getMessage(), e );
}
}
/**
* Creates a new <code>XMLFragment</code> which is not initialized.
*/
public XMLFragment() {
// nothing to do
}
/**
* Creates a new <code>XMLFragment</code> which is loaded from the given <code>URL</code>.
*
* @param url
* @throws IOException
* @throws SAXException
*/
public XMLFragment( URL url ) throws IOException, SAXException {
load( url );
}
/**
* Creates a new <code>XMLFragment</code> which is loaded from the given <code>Reader</code>.
*
* @param reader
* @param systemId
* @throws SAXException
* @throws IOException
*/
public XMLFragment( Reader reader, String systemId ) throws SAXException, IOException {
load( reader, systemId );
}
/**
* Creates a new <code>XMLFragment</code> instance based on the submitted
* <code>Document</code>.
*
* @param doc
* @param systemId
* @throws MalformedURLException
* if systemId is no valid and absolute <code>URL</code>
*/
public XMLFragment( Document doc, String systemId ) throws MalformedURLException {
setRootElement( doc.getDocumentElement() );
setSystemId( systemId );
}
/**
* Creates a new <code>XMLFragment</code> instance based on the submitted <code>Element</code>.
*
* @param element
*/
public XMLFragment( Element element ) {
setRootElement( element );
}
/**
* Returns the systemId (the URL of the <code>XMLFragment</code>).
*
* @return the systemId
*/
public URL getSystemId() {
return systemId;
}
/**
* @param systemId
* The systemId (physical location) to set (may be null).
* @throws MalformedURLException
*/
public void setSystemId( String systemId )
throws MalformedURLException {
if ( systemId != null ) {
this.systemId = new URL( systemId );
}
}
/**
* @param systemId
* The systemId (physical location) to set.
*/
public void setSystemId( URL systemId ) {
this.systemId = systemId;
}
/**
* Returns whether the document has a schema reference.
*
* @return true, if the document has a schema reference, false otherwise
*/
public boolean hasSchema() {
if ( this.rootElement.getAttribute( "xsi:schemaLocation" ) != null ) {
return true;
}
return false;
}
/**
* Determines the namespace <code>URI</code>s and the bound schema <code>URL</code>s from
* the 'xsi:schemaLocation' attribute of the document element.
*
* @return keys are URIs (namespaces), values are URLs (schema locations)
* @throws XMLParsingException
*/
public Map<URI, URL> getAttachedSchemas()
throws XMLParsingException {
Map<URI, URL> schemaMap = new HashMap<URI, URL>();
NamedNodeMap attrMap = rootElement.getAttributes();
Node schemaLocationAttr = attrMap.getNamedItem( "xsi:schemaLocation" );
if ( schemaLocationAttr == null ) {
return schemaMap;
}
String target = schemaLocationAttr.getNodeValue();
StringTokenizer tokenizer = new StringTokenizer( target );
while ( tokenizer.hasMoreTokens() ) {
URI nsURI = null;
String token = tokenizer.nextToken();
try {
nsURI = new URI( token );
} catch ( URISyntaxException e ) {
String msg = "Invalid 'xsi:schemaLocation' attribute: namespace " + token
+ "' is not a valid URI.";
LOG.logError( msg );
throw new XMLParsingException( msg );
}
URL schemaURL = null;
try {
token = tokenizer.nextToken();
schemaURL = resolve( token );
} catch ( NoSuchElementException e ) {
String msg = "Invalid 'xsi:schemaLocation' attribute: namespace '" + nsURI
+ "' is missing a schema URL.";
LOG.logError( msg );
throw new XMLParsingException( msg );
} catch ( MalformedURLException ex ) {
String msg = "Invalid 'xsi:schemaLocation' attribute: '" + token
+ "' for namespace '" + nsURI + "' could not be parsed as URL.";
throw new XMLParsingException( msg );
}
schemaMap.put( nsURI, schemaURL );
}
return schemaMap;
}
/**
* Initializes the <code>XMLFragment</code> with the content from the given <code>URL</code>.
* Sets the SystemId, too.
*
* @param url
* @throws IOException
* @throws SAXException
*/
public void load( URL url )
throws IOException, SAXException {
String uri = url.toString();
DocumentBuilder builder = XMLTools.getDocumentBuilder();
Document doc = builder.parse( uri );
setRootElement( doc.getDocumentElement() );
setSystemId( uri );
}
/**
* Initializes the <code>XMLFragment</code> with the content from the given
* <code>InputStream</code>. Sets the SystemId, too.
*
* @param istream
* @param systemId
* cannot be null
* @throws SAXException
* @throws IOException
* @throws XMLException
* @throws NullPointerException
*/
public void load( InputStream istream, String systemId )
throws SAXException, IOException, XMLException {
InputStreamReader isr = new InputStreamReader( istream, CharsetUtils.getSystemCharset() );
load( isr, systemId );
}
/**
* Initializes the <code>XMLFragment</code> with the content from the given
* <code>Reader</code>. Sets the SystemId, too.
*
* @param reader
* @param systemId
* can not be null
* @throws SAXException
* @throws IOException
* @throws NullPointerException
*/
public void load( Reader reader, String systemId )
throws SAXException, IOException {
InputSource source = new InputSource( reader );
if ( systemId == null ) {
throw new NullPointerException( "'systemId' must not be null!" );
}
DocumentBuilder builder = XMLTools.getDocumentBuilder();
Document doc = builder.parse( source );
setRootElement( doc.getDocumentElement() );
}
/**
* @param rootElement
*/
public void setRootElement( Element rootElement ) {
this.rootElement = rootElement;
}
/**
* @return the element
*/
public Element getRootElement() {
return rootElement;
}
/**
* Resolves the given URL (which may be relative) against the SystemID of the
* <code>XMLFragment</code> into a <code>URL</code> (which is always absolute).
*
* @param url
* @return the resolved URL object
* @throws MalformedURLException
*/
public URL resolve( String url )
throws MalformedURLException {
LOG.logDebug( StringTools.concat( 200, "Resolving URL '", url, "' against SystemID '",
systemId, "' of XMLFragment" ) );
// check if url is an absolut path
File file = new File( url );
if ( file.isAbsolute() ) {
return file.toURL();
}
// remove leading '/' because otherwise
// URL resolvedURL = new URL( systemId, url ); will fail
if ( url.startsWith( "/" ) ) {
url = url.substring( 1, url.length() );
LOG.logInfo( "URL has been corrected by removing the leading '/'" );
}
URL resolvedURL = new URL( systemId, url );
LOG.logDebug( StringTools.concat( 100, "-> resolvedURL: '", resolvedURL, "'" ) );
return resolvedURL;
}
/**
* Writes the <code>XMLFragment</code> instance to the given <code>Writer</code> using the
* default system encoding and adding CDATA-sections in for text-nodes where needed.
*
* TODO: Add code for CDATA safety.
*
* @param writer
*/
public void write( Writer writer ) {
Properties properties = new Properties();
properties.setProperty( OutputKeys.ENCODING, CharsetUtils.getSystemCharset() );
write( writer, properties );
}
/**
* Writes the <code>XMLFragment</code> instance to the given <code>Writer</code> using the
* specified <code>OutputKeys</code>.
*
* @param writer
* cannot be null
* @param outputProperties
* output properties for the <code>Transformer</code> that is used to serialize the
* document
*
* see javax.xml.OutputKeys
*/
public void write( Writer writer, Properties outputProperties ) {
try {
Source source = new DOMSource( rootElement );
Transformer transformer = TransformerFactory.newInstance().newTransformer();
if ( outputProperties != null ) {
transformer.setOutputProperties( outputProperties );
}
transformer.transform( source, new StreamResult( writer ) );
} catch ( TransformerConfigurationException e ) {
LOG.logError( e.getMessage(), e );
throw new XMLException( e );
} catch ( Exception e ) {
LOG.logError( e.getMessage(), e );
throw new XMLException( e );
}
}
/**
* Writes the <code>XMLFragment</code> instance to the given <code>OutputStream</code> using
* the default system encoding and adding CDATA-sections in for text-nodes where needed.
*
* TODO: Add code for CDATA safety.
*
* @param os
*/
public void write( OutputStream os ) {
Properties properties = new Properties();
properties.setProperty( OutputKeys.ENCODING, CharsetUtils.getSystemCharset() );
write( os, properties );
}
/**
* Writes the <code>XMLFragment</code> instance to the given <code>OutputStream</code> using
* the specified <code>OutputKeys</code> which allow complete control of the generated output.
*
* @param os
* cannot be null
* @param outputProperties
* output properties for the <code>Transformer</code> used to serialize the
* document
*
* @see javax.xml.transform.OutputKeys
*/
public void write( OutputStream os, Properties outputProperties ) {
try {
Source source = new DOMSource( rootElement );
Transformer transformer = TransformerFactory.newInstance().newTransformer();
if ( outputProperties != null ) {
transformer.setOutputProperties( outputProperties );
}
transformer.transform( source, new StreamResult( os ) );
} catch ( TransformerConfigurationException e ) {
LOG.logError( e.getMessage(), e );
throw new XMLException( e );
} catch ( Exception e ) {
LOG.logError( e.getMessage(), e );
throw new XMLException( e );
}
}
/**
* Writes the <code>XMLFragment</code> instance to the given <code>OutputStream</code> using
* indentation so it may be read easily.
*
* @param os
* @throws TransformerException
*/
public void prettyPrint( OutputStream os )
throws TransformerException {
PRETTY_PRINTER_XSLT.transform( this ).write( os );
}
/**
* Writes the <code>XMLFragment</code> instance to the given <code>Writer</code> using
* indentation so it may be read easily.
*
* @param writer
* @throws TransformerException
*/
public void prettyPrint( Writer writer )
throws TransformerException {
PRETTY_PRINTER_XSLT.transform( this ).write( writer );
}
/**
* Parses the submitted <code>Element</code> as a <code>SimpleLink</code>.
* <p>
* Possible escaping of the attributes "xlink:href", "xlink:role" and "xlink:arcrole" is
* performed automatically.
* </p>
*
* @param element
* @return the object representation of the element
* @throws XMLParsingException
*/
protected SimpleLink parseSimpleLink( Element element )
throws XMLParsingException {
URI href = null;
URI role = null;
URI arcrole = null;
String title = null;
String show = null;
String actuate = null;
String uriString = null;
try {
uriString = XMLTools.getNodeAsString( element, "@xlink:href", nsContext, null );
if ( uriString != null ) {
href = new URI( null, uriString, null );
}
uriString = XMLTools.getNodeAsString( element, "@xlink:role", nsContext, null );
if ( uriString != null ) {
role = new URI( null, uriString, null );
}
uriString = XMLTools.getNodeAsString( element, "@xlink:arcrole", nsContext, null );
if ( uriString != null ) {
arcrole = new URI( null, uriString, null );
}
} catch ( URISyntaxException e ) {
throw new XMLParsingException( "'" + uriString + "' is not a valid URI." );
}
return new SimpleLink( href, role, arcrole, title, show, actuate );
}
/**
* Parses the value of the submitted <code>Node</code> as a <code>QualifiedName</code>.
* <p>
* To parse the text contents of an <code>Element</code> node, the actual text node must be
* given, not the <code>Element</code> node itself.
* </p>
*
* @param node
* @return object representation of the element
* @throws XMLParsingException
*/
public static QualifiedName parseQualifiedName( Node node )
throws XMLParsingException {
String name = node.getNodeValue().trim();
QualifiedName qName = null;
if ( name.indexOf( ':' ) > -1 ) {
String[] tmp = StringTools.toArray( name, ":", false );
try {
qName = new QualifiedName( tmp[0], tmp[1], XMLTools.getNamespaceForPrefix( tmp[0],
node ) );
} catch ( URISyntaxException e ) {
throw new XMLParsingException( e.getMessage(), e );
}
} else {
qName = new QualifiedName( name );
}
return qName;
}
/**
* Returns the qualified name of the given element.
*
* @param element
* @return the qualified name of the given element.
* @throws XMLParsingException
*/
protected QualifiedName getQualifiedName( Element element )
throws XMLParsingException {
// TODO check if we can use element.getNamespaceURI() instead
URI nsURI = null;
String prefix = element.getPrefix();
try {
nsURI = XMLTools.getNamespaceForPrefix( prefix, element );
} catch ( URISyntaxException e ) {
String msg = Messages.format( "ERROR_NSURI_NO_URI", element.getPrefix() );
LOG.logError( msg, e );
throw new XMLParsingException( msg, e );
}
QualifiedName ftName = new QualifiedName( prefix, element.getLocalName(), nsURI );
return ftName;
}
/**
* returns a string representation of the XML Document
*
* @return the string
*/
public String getAsString() {
StringWriter writer = new StringWriter( 50000 );
Source source = new DOMSource( rootElement );
try {
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.transform( source, new StreamResult( writer ) );
} catch ( Exception e ) {
LOG.logError( "Error serializing XMLFragment!", e );
}
return writer.toString();
}
/**
* Returns a string representation of the object.
*
* @return a string representation of the object.
*/
@Override
public String toString() {
return getAsString();
}
}
/***********************************************************************************************
Changes to this class. What the people have been up to:
$Log: XMLFragment.java,v $
Revision 1.56 2006/10/31 16:51:42 mschneider
Fixed encoding handling in #load(URL).
Revision 1.55 2006/10/17 20:31:19 poth
*** empty log message ***
Revision 1.54 2006/09/05 17:41:24 mschneider
Fixed warnings + footer.
Revision 1.53 2006/09/05 11:51:21 schmitz
Added a whitespace trim while parsing qualified names.
Revision 1.52 2006/07/26 18:53:20 mschneider
Changed info messages to debug.
Revision 1.51 2006/07/12 14:46:16 poth
comment footer added
***********************************************************************************************/