diff --git a/core/src/main/java/org/jclouds/http/functions/ParseSax.java b/core/src/main/java/org/jclouds/http/functions/ParseSax.java index d2608d51c6..cb2045e2c1 100644 --- a/core/src/main/java/org/jclouds/http/functions/ParseSax.java +++ b/core/src/main/java/org/jclouds/http/functions/ParseSax.java @@ -21,7 +21,6 @@ package org.jclouds.http.functions; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Throwables.propagate; import static com.google.common.io.Closeables.closeQuietly; import java.io.InputStream; @@ -30,7 +29,6 @@ import java.io.StringReader; import javax.annotation.Resource; import javax.inject.Inject; -import org.jclouds.http.HttpException; import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpResponse; import org.jclouds.logging.Logger; @@ -38,14 +36,15 @@ import org.jclouds.rest.InvocationContext; import org.jclouds.rest.internal.GeneratedHttpRequest; import org.jclouds.util.Utils; import org.xml.sax.InputSource; +import org.xml.sax.SAXParseException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; import com.google.common.base.Function; /** - * This object will parse the body of an HttpResponse and return the result of - * type back to the caller. + * This object will parse the body of an HttpResponse and return the result of type back to the + * caller. * * @author Adrian Cole */ @@ -108,6 +107,7 @@ public class ParseSax implements Function, InvocationContext public T parse(InputSource from) { try { checkNotNull(from, "xml inputsource"); + from.setEncoding("UTF-8"); parser.setContentHandler(getHandler()); // This method should accept documents with a BOM (Byte-order mark) parser.parse(from); @@ -118,16 +118,25 @@ public class ParseSax implements Function, InvocationContext } private T addRequestDetailsToException(Exception e) { + String exceptionMessage = e.getMessage(); + if (e instanceof SAXParseException) { + SAXParseException parseException = (SAXParseException) e; + String systemId = parseException.getSystemId(); + if (systemId == null) { + systemId = ""; + } + exceptionMessage = String.format("Error on line %d of document %s: %s", systemId, parseException + .getLineNumber(), parseException.getMessage()); + } if (request != null) { StringBuilder message = new StringBuilder(); message.append("Error parsing input for ").append(request.getRequestLine()).append(": "); - message.append(e.getMessage()); + message.append(exceptionMessage); logger.error(e, message.toString()); - throw new HttpException(message.toString(), e); + throw new RuntimeException(message.toString(), e); } else { - propagate(e); - assert false : "should have propagated: " + e; - return null; + logger.error(e, exceptionMessage.toString()); + throw new RuntimeException(exceptionMessage.toString(), e); } } @@ -136,8 +145,7 @@ public class ParseSax implements Function, InvocationContext } /** - * Handler that produces a useable domain object accessible after parsing - * completes. + * Handler that produces a useable domain object accessible after parsing completes. * * @author Adrian Cole */ diff --git a/core/src/main/java/org/jclouds/http/internal/JavaUrlHttpCommandExecutorService.java b/core/src/main/java/org/jclouds/http/internal/JavaUrlHttpCommandExecutorService.java index 6f28d2f78c..47bad3d042 100644 --- a/core/src/main/java/org/jclouds/http/internal/JavaUrlHttpCommandExecutorService.java +++ b/core/src/main/java/org/jclouds/http/internal/JavaUrlHttpCommandExecutorService.java @@ -225,8 +225,8 @@ public class JavaUrlHttpCommandExecutorService extends BaseHttpCommandExecutorSe } } else { connection.setRequestProperty(HttpHeaders.CONTENT_LENGTH, "0"); - // for some reason POST undoes the content length header above. - if (connection.getRequestMethod().equals("POST")) + // for some reason POST/PUT undoes the content length header above. + if (connection.getRequestMethod().equals("POST") || connection.getRequestMethod().equals("PUT")) connection.setChunkedStreamingMode(0); } return connection; diff --git a/core/src/main/java/org/jclouds/util/Utils.java b/core/src/main/java/org/jclouds/util/Utils.java index 3032fce497..e439a5f561 100644 --- a/core/src/main/java/org/jclouds/util/Utils.java +++ b/core/src/main/java/org/jclouds/util/Utils.java @@ -47,10 +47,10 @@ import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.Properties; import java.util.Set; +import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/core/src/test/java/org/jclouds/http/BaseJettyTest.java b/core/src/test/java/org/jclouds/http/BaseJettyTest.java index 94878ff126..0a574cc437 100644 --- a/core/src/test/java/org/jclouds/http/BaseJettyTest.java +++ b/core/src/test/java/org/jclouds/http/BaseJettyTest.java @@ -34,6 +34,7 @@ import static org.jclouds.util.Utils.toStringAndClose; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Enumeration; import java.util.Map; import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; @@ -64,6 +65,8 @@ import org.testng.annotations.Optional; import org.testng.annotations.Parameters; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.Multimap; import com.google.common.io.InputSupplier; import com.google.inject.Injector; import com.google.inject.Module; @@ -94,7 +97,7 @@ public abstract class BaseJettyTest { Handler server1Handler = new AbstractHandler() { public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) - throws IOException, ServletException { + throws IOException, ServletException { if (failIfNoContentLength(request, response)) { return; } else if (target.indexOf("sleep") > 0) { @@ -138,8 +141,7 @@ public abstract class BaseJettyTest { response.getWriter().println("test"); } else if (request.getMethod().equals("HEAD")) { /* - * NOTE: by HTML specification, HEAD response MUST NOT include a - * body + * NOTE: by HTML specification, HEAD response MUST NOT include a body */ response.setContentType("text/xml"); response.setStatus(HttpServletResponse.SC_OK); @@ -185,7 +187,7 @@ public abstract class BaseJettyTest { } } else { for (String header : new String[] { "Content-Disposition", HttpHeaders.CONTENT_LANGUAGE, - HttpHeaders.CONTENT_ENCODING }) + HttpHeaders.CONTENT_ENCODING }) if (request.getHeader(header) != null) { response.addHeader("x-" + header, request.getHeader(header)); } @@ -206,7 +208,7 @@ public abstract class BaseJettyTest { protected void setupAndStartSSLServer(final int testPort) throws Exception { Handler server2Handler = new AbstractHandler() { public void handle(String target, HttpServletRequest request, HttpServletResponse response, int dispatch) - throws IOException, ServletException { + throws IOException, ServletException { if (request.getMethod().equals("PUT")) { if (request.getContentLength() > 0) { response.setStatus(HttpServletResponse.SC_OK); @@ -220,8 +222,7 @@ public abstract class BaseJettyTest { } } else if (request.getMethod().equals("HEAD")) { /* - * NOTE: by HTML specification, HEAD response MUST NOT include a - * body + * NOTE: by HTML specification, HEAD response MUST NOT include a body */ response.setContentType("text/xml"); response.setStatus(HttpServletResponse.SC_OK); @@ -261,12 +262,12 @@ public abstract class BaseJettyTest { } public static RestContextBuilder newBuilder(int testPort, - Properties properties, Module... connectionModules) { + Properties properties, Module... connectionModules) { properties.setProperty(Constants.PROPERTY_TRUST_ALL_CERTS, "true"); properties.setProperty(Constants.PROPERTY_RELAX_HOSTNAME, "true"); RestContextSpec contextSpec = contextSpec("test", - "http://localhost:" + testPort, "1", "identity", null, IntegrationTestClient.class, - IntegrationTestAsyncClient.class, ImmutableSet. copyOf(connectionModules)); + "http://localhost:" + testPort, "1", "identity", null, IntegrationTestClient.class, + IntegrationTestAsyncClient.class, ImmutableSet. copyOf(connectionModules)); return createContextBuilder(contextSpec, properties); } @@ -300,7 +301,7 @@ public abstract class BaseJettyTest { } protected boolean redirectEveryTwentyRequests(HttpServletRequest request, HttpServletResponse response) - throws IOException { + throws IOException { if (cycle.incrementAndGet() % 20 == 0) { response.sendRedirect("http://localhost:" + (testPort + 1) + "/"); ((Request) request).setHandled(true); @@ -309,8 +310,20 @@ public abstract class BaseJettyTest { return false; } + @SuppressWarnings("unchecked") protected boolean failIfNoContentLength(HttpServletRequest request, HttpServletResponse response) throws IOException { - if (request.getHeader(CONTENT_LENGTH) == null) { + Multimap realHeaders = LinkedHashMultimap.create(); + Enumeration headers = request.getHeaderNames(); + while (headers.hasMoreElements()) { + String header = headers.nextElement().toString(); + Enumeration values = request.getHeaders(header); + while (values.hasMoreElements()) { + realHeaders.put(header, values.nextElement().toString()); + } + } + if (realHeaders.get(CONTENT_LENGTH) == null) { + response.getWriter().println("no content length!"); + response.getWriter().println(realHeaders.toString()); response.sendError(500); ((Request) request).setHandled(true); return true;