Issue 812:add options to place xml source text to exception

This commit is contained in:
Adrian Cole 2012-01-14 21:02:52 -08:00
parent 72c1583ffd
commit b3d6ad2ae8
4 changed files with 71 additions and 27 deletions

View File

@ -21,18 +21,20 @@ package org.jclouds.http.functions;
import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.io.Closeables.closeQuietly; import static com.google.common.io.Closeables.closeQuietly;
import static org.jclouds.http.HttpUtils.closeClientButKeepContentStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.StringReader; import java.io.StringReader;
import javax.inject.Inject; import javax.annotation.Resource;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse; import org.jclouds.http.HttpResponse;
import org.jclouds.javax.annotation.Nullable;
import org.jclouds.logging.Logger;
import org.jclouds.rest.InvocationContext; import org.jclouds.rest.InvocationContext;
import org.jclouds.rest.internal.GeneratedHttpRequest; import org.jclouds.rest.internal.GeneratedHttpRequest;
import org.jclouds.util.Strings2;
import org.xml.sax.InputSource; import org.xml.sax.InputSource;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException; import org.xml.sax.SAXParseException;
@ -41,6 +43,7 @@ import org.xml.sax.helpers.DefaultHandler;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import com.google.common.io.Closeables;
/** /**
* This object will parse the body of an HttpResponse and return the result of type <T> back to the * This object will parse the body of an HttpResponse and return the result of type <T> back to the
@ -50,6 +53,9 @@ import com.google.common.base.Throwables;
*/ */
public class ParseSax<T> implements Function<HttpResponse, T>, InvocationContext<ParseSax<T>> { public class ParseSax<T> implements Function<HttpResponse, T>, InvocationContext<ParseSax<T>> {
@Resource
private Logger logger = Logger.NULL;
private final XMLReader parser; private final XMLReader parser;
private final HandlerWithResult<T> handler; private final HandlerWithResult<T> handler;
private HttpRequest request; private HttpRequest request;
@ -58,7 +64,6 @@ public class ParseSax<T> implements Function<HttpResponse, T>, InvocationContext
<T> ParseSax<T> create(HandlerWithResult<T> handler); <T> ParseSax<T> create(HandlerWithResult<T> handler);
} }
@Inject
public ParseSax(XMLReader parser, HandlerWithResult<T> handler) { public ParseSax(XMLReader parser, HandlerWithResult<T> handler) {
this.parser = checkNotNull(parser, "parser"); this.parser = checkNotNull(parser, "parser");
this.handler = checkNotNull(handler, "handler"); this.handler = checkNotNull(handler, "handler");
@ -71,34 +76,45 @@ public class ParseSax<T> implements Function<HttpResponse, T>, InvocationContext
} catch (NullPointerException e) { } catch (NullPointerException e) {
return addDetailsAndPropagate(from, e); return addDetailsAndPropagate(from, e);
} }
if (from.getStatusCode() >= 300) InputStream is = null;
return convertStreamToStringAndParse(from);
InputStream is = from.getPayload().getInput();
try { try {
// debug is more normally set, so trace is more appropriate for
// something heavy like this
if (from.getStatusCode() >= 300 || logger.isTraceEnabled())
return convertStreamToStringAndParse(from);
is = from.getPayload().getInput();
return parse(new InputSource(is)); return parse(new InputSource(is));
} catch (RuntimeException e) { } catch (RuntimeException e) {
return addDetailsAndPropagate(from, e); return addDetailsAndPropagate(from, e);
} finally { } finally {
closeQuietly(is); Closeables.closeQuietly(is);
from.getPayload().release();
} }
} }
private T convertStreamToStringAndParse(HttpResponse from) { private T convertStreamToStringAndParse(HttpResponse response) {
String from = null;
try { try {
return parse(Strings2.toStringAndClose(from.getPayload().getInput())); from = new String(closeClientButKeepContentStream(response));
validateXml(from);
return doParse(new InputSource(new StringReader(from)));
} catch (Exception e) { } catch (Exception e) {
return addDetailsAndPropagate(from, e); return addDetailsAndPropagate(response, e, from);
} }
} }
public T parse(String from) { public T parse(String from) {
try { try {
checkNotNull(from, "xml string"); validateXml(from);
checkArgument(from.indexOf('<') >= 0, String.format("not an xml document [%s] ", from)); return doParse(new InputSource(new StringReader(from)));
} catch (RuntimeException e) { } catch (Exception e) {
return addDetailsAndPropagate(null, e); return addDetailsAndPropagate(null, e, from);
} }
return parse(new InputSource(new StringReader(from))); }
private void validateXml(String from) {
checkNotNull(from, "xml string");
checkArgument(from.indexOf('<') >= 0, String.format("not an xml document [%s] ", from));
} }
public T parse(InputStream from) { public T parse(InputStream from) {
@ -127,6 +143,10 @@ public class ParseSax<T> implements Function<HttpResponse, T>, InvocationContext
} }
public T addDetailsAndPropagate(HttpResponse response, Exception e) { public T addDetailsAndPropagate(HttpResponse response, Exception e) {
return addDetailsAndPropagate(response, e, null);
}
public T addDetailsAndPropagate(HttpResponse response, Exception e, @Nullable String text) {
StringBuilder message = new StringBuilder(); StringBuilder message = new StringBuilder();
if (request != null) { if (request != null) {
message.append("request: ").append(request.getRequestLine()); message.append("request: ").append(request.getRequestLine());
@ -144,15 +164,16 @@ public class ParseSax<T> implements Function<HttpResponse, T>, InvocationContext
} }
if (message.length() != 0) if (message.length() != 0)
message.append("; "); message.append("; ");
message.append(String.format("error at %d:%d in document %s", parseException.getColumnNumber(), message.append(String.format("error at %d:%d in document %s", parseException.getColumnNumber(), parseException
parseException.getLineNumber(), systemId)); .getLineNumber(), systemId));
} }
if (text != null)
message.append("; source:\n").append(text);
if (message.length() != 0) { if (message.length() != 0) {
message.append("; cause: ").append(e.toString()); message.append("; cause: ").append(e.toString());
throw new RuntimeException(message.toString(), e); throw new RuntimeException(message.toString(), e);
} else { } else {
Throwables.propagate(e); throw Throwables.propagate(e);
return null;
} }
} }
@ -167,7 +188,7 @@ public class ParseSax<T> implements Function<HttpResponse, T>, InvocationContext
* @author Adrian Cole * @author Adrian Cole
*/ */
public abstract static class HandlerWithResult<T> extends DefaultHandler implements public abstract static class HandlerWithResult<T> extends DefaultHandler implements
InvocationContext<HandlerWithResult<T>> { InvocationContext<HandlerWithResult<T>> {
private HttpRequest request; private HttpRequest request;
protected HttpRequest getRequest() { protected HttpRequest getRequest() {

View File

@ -27,7 +27,9 @@ import org.jclouds.http.functions.ParseSax;
import org.jclouds.http.functions.ParseSax.HandlerWithResult; import org.jclouds.http.functions.ParseSax.HandlerWithResult;
import org.xml.sax.XMLReader; import org.xml.sax.XMLReader;
import com.google.common.base.Throwables;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
import com.google.inject.Injector;
import com.google.inject.Provides; import com.google.inject.Provides;
import com.google.inject.Scopes; import com.google.inject.Scopes;
@ -42,18 +44,27 @@ public class SaxParserModule extends AbstractModule {
bind(ParseSax.Factory.class).to(Factory.class).in(Scopes.SINGLETON); bind(ParseSax.Factory.class).to(Factory.class).in(Scopes.SINGLETON);
} }
private static class Factory implements ParseSax.Factory { static class Factory implements ParseSax.Factory {
private final SAXParserFactory factory;
private final Injector i;
@Inject @Inject
private SAXParserFactory factory; Factory(SAXParserFactory factory, Injector i) {
this.factory = factory;
this.i = i;
}
public <T> ParseSax<T> create(HandlerWithResult<T> handler) { public <T> ParseSax<T> create(HandlerWithResult<T> handler) {
SAXParser saxParser; SAXParser saxParser;
try { try {
saxParser = factory.newSAXParser(); saxParser = factory.newSAXParser();
XMLReader parser = saxParser.getXMLReader(); XMLReader parser = saxParser.getXMLReader();
return new ParseSax<T>(parser, handler); // TODO: switch to @AssistedInject
ParseSax<T> returnVal = new ParseSax<T>(parser, handler);
i.injectMembers(returnVal);
return returnVal;
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw Throwables.propagate(e);
} }
} }

View File

@ -120,7 +120,13 @@
<priority value="DEBUG" /> <priority value="DEBUG" />
<appender-ref ref="ASYNC" /> <appender-ref ref="ASYNC" />
</category> </category>
<!-- set to trace to get more info when parser fail -->
<category name="org.jclouds.http.functions.ParseSax">
<priority value="TRACE" />
<appender-ref ref="ASYNC" />
</category>
<category name="jclouds.headers"> <category name="jclouds.headers">
<priority value="DEBUG" /> <priority value="DEBUG" />
<appender-ref ref="ASYNCWIRE" /> <appender-ref ref="ASYNCWIRE" />

View File

@ -120,7 +120,13 @@
<priority value="DEBUG" /> <priority value="DEBUG" />
<appender-ref ref="ASYNC" /> <appender-ref ref="ASYNC" />
</category> </category>
<!-- set to trace to get more info when parser fail -->
<category name="org.jclouds.http.functions.ParseSax">
<priority value="TRACE" />
<appender-ref ref="ASYNC" />
</category>
<category name="jclouds.headers"> <category name="jclouds.headers">
<priority value="DEBUG" /> <priority value="DEBUG" />
<appender-ref ref="ASYNCWIRE" /> <appender-ref ref="ASYNCWIRE" />
@ -132,7 +138,7 @@
</category> </category>
<category name="jclouds.wire"> <category name="jclouds.wire">
<priority value="DEBUG" /> <priority value="TRACE" />
<appender-ref ref="ASYNCWIRE" /> <appender-ref ref="ASYNCWIRE" />
</category> </category>