/**
* Copyright 2011 meltmedia
*
* 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.xchain.framework.net;
import java.net.URL;
import java.net.URLConnection;
import java.io.InputStream;
import java.io.IOException;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import org.xml.sax.InputSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A collection of static methods for converting URLs into SAX InputSources and JAXP Source objects.
*
* @author Christian Trimble
* @author Devon Tackett
* @author Josh Kennedy
* @author John Trimble
*/
public class UrlSourceUtil
{
public static Logger log = LoggerFactory.getLogger(UrlSourceUtil.class);
/**
* Create an InputSource from the given URL.
*/
public static InputSource createSaxInputSource( URL url )
throws IOException
{
if( log.isDebugEnabled() ) {
log.debug("Creating input source for url '"+url.toString()+"'.");
}
// create the new InputSource.
InputSource source = new InputSource();
// get a connection to the url.
URLConnection connection = url.openConnection();
// open the connection.
connection.connect();
// set the byte stream from the URL.
source.setByteStream( new ReportingInputStream(url.toExternalForm(), connection.getInputStream()));
//source.setByteStream( connection.getInputStream());
// set the encoding.
source.setEncoding( connection.getContentEncoding() );
// set the url to the system identifier.
source.setSystemId( url.toExternalForm() );
// return the source.
return source;
}
/**
* A utility function that builds javax.xml.transform sources for urls. This method contructs the
* Source object by using the URLs openConnection().getInputStream() method. This allows the urls
* content handler to be the source of the byte stream. The systemId is set to the external form
* of the url to aid in relative url resolution.
*/
public static Source createTransformSource( URL url )
throws IOException
{
// get a connection to the url.
URLConnection connection = url.openConnection();
// open the connection.
connection.connect();
// NOTE: it seems strange to not specify the encoding of the stream, but the StreamSource docs state that
// this information should be taken from the XML declaration.
return createTransformSource( url.toExternalForm(), connection.getInputStream() );
}
public static Source createTransformSource( String systemId, InputStream inputStream )
{
if( log.isDebugEnabled() ) {
log.debug("Creating source for url '"+systemId+"'.");
}
// create the new stream source.
StreamSource source = new StreamSource();
// set the intput stream from the url.
source.setInputStream( new ReportingInputStream(systemId, inputStream) );
//source.setInputStream( inputStream );
// set the system identifier.
source.setSystemId( systemId );
// return the source.
return source;
}
/**
* An InputStream wrapper which will report the SystemId and the first few lines of the file if
* it finalized before being properly closed. This normally occurs when an exception is encountered
* while processing the wrapped InputStream.
*/
public static class ReportingInputStream
extends InputStream
{
/** The maximum number of lines to display on error. */
private static final int BUFFERED_LINE_COUNT = 10;
/** The systemId of the resource. */
private String systemId;
private volatile boolean closed = false;
private InputStream wrapped;
private String stackTrace;
public ReportingInputStream( String systemId, InputStream wrapped )
{
if( wrapped == null )
throw new IllegalArgumentException("InputStream for \""+systemId+"\" cannot be null!");
this.systemId = systemId;
this.wrapped = wrapped;
// create the first few lines of a stack trace to print if the close fails.
StringBuffer stackTraceBuffer = new StringBuffer();
StackTraceElement[] stackTraceArray = new Throwable().fillInStackTrace().getStackTrace();
for( int i = 0; i < stackTraceArray.length && i < BUFFERED_LINE_COUNT; i++ ) {
stackTraceBuffer.append(stackTraceArray[i]).append("\n");
}
stackTrace = stackTraceBuffer.toString();
}
public int available()
throws IOException
{
return wrapped.available();
}
public void close()
throws IOException
{
closed = true;
wrapped.close();
}
public void mark( int readLimit ) {
wrapped.mark( readLimit );
}
public boolean markSupported()
{
return wrapped.markSupported();
}
public int read()
throws IOException
{
return wrapped.read();
}
public int read( byte[] b )
throws IOException
{
return wrapped.read( b );
}
public int read( byte[] b, int off, int len )
throws IOException
{
return wrapped.read( b, off, len );
}
public void reset()
throws IOException
{
wrapped.reset();
}
public long skip( long n )
throws IOException
{
return wrapped.skip( n );
}
public void finalize()
{
if( !closed ) {
if( log.isErrorEnabled() ) {
log.error("IO STREAM CLOSED BY FINALIZE FOR '"+systemId+"'\n"+stackTrace);
}
}
wrapped = null;
}
}
}