src/share/jaxws_classes/com/sun/xml/internal/ws/transport/http/client/HttpTransportPipe.java

Fri, 04 Oct 2013 16:21:34 +0100

author
mkos
date
Fri, 04 Oct 2013 16:21:34 +0100
changeset 408
b0610cd08440
parent 368
0989ad8c0860
child 637
9c07ef4934dd
permissions
-rw-r--r--

8025054: Update JAX-WS RI integration to 2.2.9-b130926.1035
Reviewed-by: chegar

ohair@286 1 /*
mkos@408 2 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
ohair@286 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
ohair@286 4 *
ohair@286 5 * This code is free software; you can redistribute it and/or modify it
ohair@286 6 * under the terms of the GNU General Public License version 2 only, as
ohair@286 7 * published by the Free Software Foundation. Oracle designates this
ohair@286 8 * particular file as subject to the "Classpath" exception as provided
ohair@286 9 * by Oracle in the LICENSE file that accompanied this code.
ohair@286 10 *
ohair@286 11 * This code is distributed in the hope that it will be useful, but WITHOUT
ohair@286 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
ohair@286 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
ohair@286 14 * version 2 for more details (a copy is included in the LICENSE file that
ohair@286 15 * accompanied this code).
ohair@286 16 *
ohair@286 17 * You should have received a copy of the GNU General Public License version
ohair@286 18 * 2 along with this work; if not, write to the Free Software Foundation,
ohair@286 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
ohair@286 20 *
ohair@286 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
ohair@286 22 * or visit www.oracle.com if you need additional information or have any
ohair@286 23 * questions.
ohair@286 24 */
ohair@286 25
ohair@286 26 package com.sun.xml.internal.ws.transport.http.client;
ohair@286 27
ohair@286 28 import com.sun.istack.internal.NotNull;
ohair@286 29 import com.sun.xml.internal.ws.api.SOAPVersion;
ohair@286 30 import com.sun.xml.internal.ws.api.WSBinding;
ohair@286 31 import com.sun.xml.internal.ws.api.ha.StickyFeature;
ohair@286 32 import com.sun.xml.internal.ws.api.message.Packet;
ohair@286 33 import com.sun.xml.internal.ws.api.pipe.*;
ohair@286 34 import com.sun.xml.internal.ws.api.pipe.helper.AbstractTubeImpl;
alanb@368 35 import com.sun.xml.internal.ws.client.ClientTransportException;
ohair@286 36 import com.sun.xml.internal.ws.developer.HttpConfigFeature;
alanb@368 37 import com.sun.xml.internal.ws.resources.ClientMessages;
mkos@408 38 import com.sun.xml.internal.ws.resources.WsservletMessages;
ohair@286 39 import com.sun.xml.internal.ws.transport.Headers;
mkos@408 40 import com.sun.xml.internal.ws.transport.http.HttpAdapter;
ohair@286 41 import com.sun.xml.internal.ws.util.ByteArrayBuffer;
ohair@286 42 import com.sun.xml.internal.ws.util.RuntimeVersion;
ohair@286 43 import com.sun.xml.internal.ws.util.StreamUtils;
ohair@286 44
ohair@286 45 import javax.xml.bind.DatatypeConverter;
ohair@286 46 import javax.xml.ws.BindingProvider;
ohair@286 47 import javax.xml.ws.WebServiceException;
ohair@286 48 import javax.xml.ws.WebServiceFeature;
alanb@368 49 import javax.xml.ws.handler.MessageContext;
ohair@286 50 import javax.xml.ws.soap.SOAPBinding;
alanb@368 51 import java.io.*;
ohair@286 52 import java.net.CookieHandler;
alanb@368 53 import java.net.HttpURLConnection;
alanb@368 54 import java.util.*;
alanb@368 55 import java.util.Map.Entry;
alanb@368 56 import java.util.logging.Level;
ohair@286 57 import java.util.logging.Logger;
ohair@286 58
ohair@286 59 /**
ohair@286 60 * {@link Tube} that sends a request to a remote HTTP server.
ohair@286 61 *
ohair@286 62 * TODO: need to create separate HTTP transport pipes for binding. SOAP1.1, SOAP1.2,
ohair@286 63 * TODO: XML/HTTP differ in handling status codes.
ohair@286 64 *
ohair@286 65 * @author Jitendra Kotamraju
ohair@286 66 */
ohair@286 67 public class HttpTransportPipe extends AbstractTubeImpl {
alanb@368 68
alanb@368 69 private static final List<String> USER_AGENT = Collections.singletonList(RuntimeVersion.VERSION.toString());
alanb@368 70 private static final Logger LOGGER = Logger.getLogger(HttpTransportPipe.class.getName());
alanb@368 71
alanb@368 72 /**
alanb@368 73 * Dumps what goes across HTTP transport.
alanb@368 74 */
alanb@368 75 public static boolean dump;
ohair@286 76
ohair@286 77 private final Codec codec;
ohair@286 78 private final WSBinding binding;
ohair@286 79 private final CookieHandler cookieJar; // shared object among the tubes
ohair@286 80 private final boolean sticky;
ohair@286 81
ohair@286 82 static {
alanb@368 83 boolean b;
ohair@286 84 try {
alanb@368 85 b = Boolean.getBoolean(HttpTransportPipe.class.getName()+".dump");
alanb@368 86 } catch( Throwable t ) {
alanb@368 87 b = false;
ohair@286 88 }
alanb@368 89 dump = b;
ohair@286 90 }
ohair@286 91
ohair@286 92 public HttpTransportPipe(Codec codec, WSBinding binding) {
ohair@286 93 this.codec = codec;
ohair@286 94 this.binding = binding;
ohair@286 95 this.sticky = isSticky(binding);
ohair@286 96 HttpConfigFeature configFeature = binding.getFeature(HttpConfigFeature.class);
ohair@286 97 if (configFeature == null) {
ohair@286 98 configFeature = new HttpConfigFeature();
ohair@286 99 }
ohair@286 100 this.cookieJar = configFeature.getCookieHandler();
ohair@286 101 }
ohair@286 102
ohair@286 103 private static boolean isSticky(WSBinding binding) {
ohair@286 104 boolean tSticky = false;
ohair@286 105 WebServiceFeature[] features = binding.getFeatures().toArray();
ohair@286 106 for(WebServiceFeature f : features) {
ohair@286 107 if (f instanceof StickyFeature) {
ohair@286 108 tSticky = true;
ohair@286 109 break;
ohair@286 110 }
ohair@286 111 }
ohair@286 112 return tSticky;
ohair@286 113 }
ohair@286 114
ohair@286 115 /*
ohair@286 116 * Copy constructor for {@link Tube#copy(TubeCloner)}.
ohair@286 117 */
ohair@286 118 private HttpTransportPipe(HttpTransportPipe that, TubeCloner cloner) {
ohair@286 119 this(that.codec.copy(), that.binding);
ohair@286 120 cloner.add(that,this);
ohair@286 121 }
ohair@286 122
alanb@368 123 @Override
ohair@286 124 public NextAction processException(@NotNull Throwable t) {
ohair@286 125 return doThrow(t);
ohair@286 126 }
ohair@286 127
alanb@368 128 @Override
ohair@286 129 public NextAction processRequest(@NotNull Packet request) {
ohair@286 130 return doReturnWith(process(request));
ohair@286 131 }
ohair@286 132
alanb@368 133 @Override
ohair@286 134 public NextAction processResponse(@NotNull Packet response) {
ohair@286 135 return doReturnWith(response);
ohair@286 136 }
ohair@286 137
ohair@286 138 protected HttpClientTransport getTransport(Packet request, Map<String, List<String>> reqHeaders) {
ohair@286 139 return new HttpClientTransport(request, reqHeaders);
ohair@286 140 }
ohair@286 141
ohair@286 142 @Override
ohair@286 143 public Packet process(Packet request) {
ohair@286 144 HttpClientTransport con;
ohair@286 145 try {
ohair@286 146 // get transport headers from message
ohair@286 147 Map<String, List<String>> reqHeaders = new Headers();
ohair@286 148 @SuppressWarnings("unchecked")
ohair@286 149 Map<String, List<String>> userHeaders = (Map<String, List<String>>) request.invocationProperties.get(MessageContext.HTTP_REQUEST_HEADERS);
ohair@286 150 boolean addUserAgent = true;
ohair@286 151 if (userHeaders != null) {
ohair@286 152 // userHeaders may not be modifiable like SingletonMap, just copy them
ohair@286 153 reqHeaders.putAll(userHeaders);
ohair@286 154 // application wants to use its own User-Agent header
ohair@286 155 if (userHeaders.get("User-Agent") != null) {
ohair@286 156 addUserAgent = false;
ohair@286 157 }
ohair@286 158 }
ohair@286 159 if (addUserAgent) {
ohair@286 160 reqHeaders.put("User-Agent", USER_AGENT);
ohair@286 161 }
ohair@286 162
ohair@286 163 addBasicAuth(request, reqHeaders);
ohair@286 164 addCookies(request, reqHeaders);
ohair@286 165
ohair@286 166 con = getTransport(request, reqHeaders);
ohair@286 167 request.addSatellite(new HttpResponseProperties(con));
ohair@286 168
ohair@286 169 ContentType ct = codec.getStaticContentType(request);
ohair@286 170 if (ct == null) {
ohair@286 171 ByteArrayBuffer buf = new ByteArrayBuffer();
ohair@286 172
ohair@286 173 ct = codec.encode(request, buf);
ohair@286 174 // data size is available, set it as Content-Length
ohair@286 175 reqHeaders.put("Content-Length", Collections.singletonList(Integer.toString(buf.size())));
ohair@286 176 reqHeaders.put("Content-Type", Collections.singletonList(ct.getContentType()));
ohair@286 177 if (ct.getAcceptHeader() != null) {
ohair@286 178 reqHeaders.put("Accept", Collections.singletonList(ct.getAcceptHeader()));
ohair@286 179 }
ohair@286 180 if (binding instanceof SOAPBinding) {
ohair@286 181 writeSOAPAction(reqHeaders, ct.getSOAPActionHeader());
ohair@286 182 }
ohair@286 183
alanb@368 184 if (dump || LOGGER.isLoggable(Level.FINER)) {
ohair@286 185 dump(buf, "HTTP request", reqHeaders);
alanb@368 186 }
ohair@286 187
ohair@286 188 buf.writeTo(con.getOutput());
ohair@286 189 } else {
ohair@286 190 // Set static Content-Type
ohair@286 191 reqHeaders.put("Content-Type", Collections.singletonList(ct.getContentType()));
ohair@286 192 if (ct.getAcceptHeader() != null) {
ohair@286 193 reqHeaders.put("Accept", Collections.singletonList(ct.getAcceptHeader()));
ohair@286 194 }
ohair@286 195 if (binding instanceof SOAPBinding) {
ohair@286 196 writeSOAPAction(reqHeaders, ct.getSOAPActionHeader());
ohair@286 197 }
ohair@286 198
ohair@286 199 if(dump || LOGGER.isLoggable(Level.FINER)) {
ohair@286 200 ByteArrayBuffer buf = new ByteArrayBuffer();
ohair@286 201 codec.encode(request, buf);
ohair@286 202 dump(buf, "HTTP request - "+request.endpointAddress, reqHeaders);
ohair@286 203 OutputStream out = con.getOutput();
ohair@286 204 if (out != null) {
ohair@286 205 buf.writeTo(out);
ohair@286 206 }
ohair@286 207 } else {
ohair@286 208 OutputStream os = con.getOutput();
ohair@286 209 if (os != null) {
ohair@286 210 codec.encode(request, os);
ohair@286 211 }
ohair@286 212 }
ohair@286 213 }
ohair@286 214
ohair@286 215 con.closeOutput();
ohair@286 216
ohair@286 217 return createResponsePacket(request, con);
ohair@286 218 } catch(WebServiceException wex) {
ohair@286 219 throw wex;
ohair@286 220 } catch(Exception ex) {
ohair@286 221 throw new WebServiceException(ex);
ohair@286 222 }
ohair@286 223 }
ohair@286 224
ohair@286 225 private Packet createResponsePacket(Packet request, HttpClientTransport con) throws IOException {
ohair@286 226 con.readResponseCodeAndMessage(); // throws IOE
ohair@286 227 recordCookies(request, con);
ohair@286 228
ohair@286 229 InputStream responseStream = con.getInput();
ohair@286 230 if (dump || LOGGER.isLoggable(Level.FINER)) {
ohair@286 231 ByteArrayBuffer buf = new ByteArrayBuffer();
ohair@286 232 if (responseStream != null) {
ohair@286 233 buf.write(responseStream);
ohair@286 234 responseStream.close();
ohair@286 235 }
ohair@286 236 dump(buf,"HTTP response - "+request.endpointAddress+" - "+con.statusCode, con.getHeaders());
ohair@286 237 responseStream = buf.newInputStream();
ohair@286 238 }
ohair@286 239
ohair@286 240 // Check if stream contains any data
ohair@286 241 int cl = con.contentLength;
ohair@286 242 InputStream tempIn = null;
ohair@286 243 if (cl == -1) { // No Content-Length header
ohair@286 244 tempIn = StreamUtils.hasSomeData(responseStream);
ohair@286 245 if (tempIn != null) {
ohair@286 246 responseStream = tempIn;
ohair@286 247 }
ohair@286 248 }
ohair@286 249 if (cl == 0 || (cl == -1 && tempIn == null)) {
ohair@286 250 if(responseStream != null) {
ohair@286 251 responseStream.close(); // No data, so close the stream
ohair@286 252 responseStream = null;
ohair@286 253 }
ohair@286 254
ohair@286 255 }
ohair@286 256
ohair@286 257 // Allows only certain http status codes for a binding. For all
ohair@286 258 // other status codes, throws exception
ohair@286 259 checkStatusCode(responseStream, con); // throws ClientTransportException
ohair@286 260
ohair@286 261 Packet reply = request.createClientResponse(null);
ohair@286 262 reply.wasTransportSecure = con.isSecure();
ohair@286 263 if (responseStream != null) {
ohair@286 264 String contentType = con.getContentType();
ohair@286 265 if (contentType != null && contentType.contains("text/html") && binding instanceof SOAPBinding) {
ohair@286 266 throw new ClientTransportException(ClientMessages.localizableHTTP_STATUS_CODE(con.statusCode, con.statusMessage));
ohair@286 267 }
ohair@286 268 codec.decode(responseStream, contentType, reply);
ohair@286 269 }
ohair@286 270 return reply;
ohair@286 271 }
ohair@286 272
ohair@286 273 /*
ohair@286 274 * Allows the following HTTP status codes.
ohair@286 275 * SOAP 1.1/HTTP - 200, 202, 500
ohair@286 276 * SOAP 1.2/HTTP - 200, 202, 400, 500
ohair@286 277 * XML/HTTP - all
ohair@286 278 *
ohair@286 279 * For all other status codes, it throws an exception
ohair@286 280 */
ohair@286 281 private void checkStatusCode(InputStream in, HttpClientTransport con) throws IOException {
ohair@286 282 int statusCode = con.statusCode;
ohair@286 283 String statusMessage = con.statusMessage;
ohair@286 284 // SOAP1.1 and SOAP1.2 differ here
ohair@286 285 if (binding instanceof SOAPBinding) {
ohair@286 286 if (binding.getSOAPVersion() == SOAPVersion.SOAP_12) {
ohair@286 287 //In SOAP 1.2, Fault messages can be sent with 4xx and 5xx error codes
ohair@286 288 if (statusCode == HttpURLConnection.HTTP_OK || statusCode == HttpURLConnection.HTTP_ACCEPTED || isErrorCode(statusCode)) {
ohair@286 289 // acceptable status codes for SOAP 1.2
ohair@286 290 if (isErrorCode(statusCode) && in == null) {
ohair@286 291 // No envelope for the error, so throw an exception with http error details
ohair@286 292 throw new ClientTransportException(ClientMessages.localizableHTTP_STATUS_CODE(statusCode, statusMessage));
ohair@286 293 }
ohair@286 294 return;
ohair@286 295 }
ohair@286 296 } else {
ohair@286 297 // SOAP 1.1
ohair@286 298 if (statusCode == HttpURLConnection.HTTP_OK || statusCode == HttpURLConnection.HTTP_ACCEPTED || statusCode == HttpURLConnection.HTTP_INTERNAL_ERROR) {
ohair@286 299 // acceptable status codes for SOAP 1.1
ohair@286 300 if (statusCode == HttpURLConnection.HTTP_INTERNAL_ERROR && in == null) {
ohair@286 301 // No envelope for the error, so throw an exception with http error details
ohair@286 302 throw new ClientTransportException(ClientMessages.localizableHTTP_STATUS_CODE(statusCode, statusMessage));
ohair@286 303 }
ohair@286 304 return;
ohair@286 305 }
ohair@286 306 }
ohair@286 307 if (in != null) {
ohair@286 308 in.close();
ohair@286 309 }
ohair@286 310 throw new ClientTransportException(ClientMessages.localizableHTTP_STATUS_CODE(statusCode, statusMessage));
ohair@286 311 }
ohair@286 312 // Every status code is OK for XML/HTTP
ohair@286 313 }
ohair@286 314
ohair@286 315 private boolean isErrorCode(int code) {
ohair@286 316 //if(code/100 == 5/*Server-side error*/ || code/100 == 4 /*client error*/ ) {
ohair@286 317 return code == 500 || code == 400;
ohair@286 318 }
ohair@286 319
ohair@286 320 private void addCookies(Packet context, Map<String, List<String>> reqHeaders) throws IOException {
ohair@286 321 Boolean shouldMaintainSessionProperty =
alanb@368 322 (Boolean) context.invocationProperties.get(BindingProvider.SESSION_MAINTAIN_PROPERTY);
ohair@286 323 if (shouldMaintainSessionProperty != null && !shouldMaintainSessionProperty) {
ohair@286 324 return; // explicitly turned off
ohair@286 325 }
ohair@286 326 if (sticky || (shouldMaintainSessionProperty != null && shouldMaintainSessionProperty)) {
alanb@368 327 Map<String, List<String>> rememberedCookies = cookieJar.get(context.endpointAddress.getURI(), reqHeaders);
alanb@368 328 processCookieHeaders(reqHeaders, rememberedCookies, "Cookie");
alanb@368 329 processCookieHeaders(reqHeaders, rememberedCookies, "Cookie2");
alanb@368 330 }
alanb@368 331 }
alanb@368 332
alanb@368 333 private void processCookieHeaders(Map<String, List<String>> requestHeaders, Map<String, List<String>> rememberedCookies, String cookieHeader) {
alanb@368 334 List<String> jarCookies = rememberedCookies.get(cookieHeader);
alanb@368 335 if (jarCookies != null && !jarCookies.isEmpty()) {
alanb@368 336 List<String> resultCookies = mergeUserCookies(jarCookies, requestHeaders.get(cookieHeader));
alanb@368 337 requestHeaders.put(cookieHeader, resultCookies);
alanb@368 338 }
alanb@368 339 }
alanb@368 340
alanb@368 341 private List<String> mergeUserCookies(List<String> rememberedCookies, List<String> userCookies) {
alanb@368 342
alanb@368 343 // nothing to merge
alanb@368 344 if (userCookies == null || userCookies.isEmpty()) {
alanb@368 345 return rememberedCookies;
alanb@368 346 }
alanb@368 347
alanb@368 348 Map<String, String> map = new HashMap<String, String>();
alanb@368 349 cookieListToMap(rememberedCookies, map);
alanb@368 350 cookieListToMap(userCookies, map);
alanb@368 351
alanb@368 352 return new ArrayList<String>(map.values());
alanb@368 353 }
alanb@368 354
alanb@368 355 private void cookieListToMap(List<String> cookieList, Map<String, String> targetMap) {
alanb@368 356 for(String cookie : cookieList) {
alanb@368 357 int index = cookie.indexOf("=");
alanb@368 358 String cookieName = cookie.substring(0, index);
alanb@368 359 targetMap.put(cookieName, cookie);
ohair@286 360 }
ohair@286 361 }
ohair@286 362
ohair@286 363 private void recordCookies(Packet context, HttpClientTransport con) throws IOException {
ohair@286 364 Boolean shouldMaintainSessionProperty =
alanb@368 365 (Boolean) context.invocationProperties.get(BindingProvider.SESSION_MAINTAIN_PROPERTY);
ohair@286 366 if (shouldMaintainSessionProperty != null && !shouldMaintainSessionProperty) {
ohair@286 367 return; // explicitly turned off
ohair@286 368 }
ohair@286 369 if (sticky || (shouldMaintainSessionProperty != null && shouldMaintainSessionProperty)) {
ohair@286 370 cookieJar.put(context.endpointAddress.getURI(), con.getHeaders());
ohair@286 371 }
ohair@286 372 }
ohair@286 373
ohair@286 374 private void addBasicAuth(Packet context, Map<String, List<String>> reqHeaders) {
ohair@286 375 String user = (String) context.invocationProperties.get(BindingProvider.USERNAME_PROPERTY);
ohair@286 376 if (user != null) {
ohair@286 377 String pw = (String) context.invocationProperties.get(BindingProvider.PASSWORD_PROPERTY);
ohair@286 378 if (pw != null) {
alanb@368 379 StringBuilder buf = new StringBuilder(user);
ohair@286 380 buf.append(":");
ohair@286 381 buf.append(pw);
ohair@286 382 String creds = DatatypeConverter.printBase64Binary(buf.toString().getBytes());
ohair@286 383 reqHeaders.put("Authorization", Collections.singletonList("Basic "+creds));
ohair@286 384 }
ohair@286 385 }
ohair@286 386 }
ohair@286 387
ohair@286 388 /*
ohair@286 389 * write SOAPAction header if the soapAction parameter is non-null or BindingProvider properties set.
ohair@286 390 * BindingProvider properties take precedence.
ohair@286 391 */
ohair@286 392 private void writeSOAPAction(Map<String, List<String>> reqHeaders, String soapAction) {
ohair@286 393 //dont write SOAPAction HTTP header for SOAP 1.2 messages.
alanb@368 394 if(SOAPVersion.SOAP_12.equals(binding.getSOAPVersion())) {
ohair@286 395 return;
alanb@368 396 }
alanb@368 397 if (soapAction != null) {
ohair@286 398 reqHeaders.put("SOAPAction", Collections.singletonList(soapAction));
alanb@368 399 } else {
ohair@286 400 reqHeaders.put("SOAPAction", Collections.singletonList("\"\""));
alanb@368 401 }
ohair@286 402 }
ohair@286 403
alanb@368 404 @Override
ohair@286 405 public void preDestroy() {
ohair@286 406 // nothing to do. Intentionally left empty.
ohair@286 407 }
ohair@286 408
alanb@368 409 @Override
ohair@286 410 public HttpTransportPipe copy(TubeCloner cloner) {
ohair@286 411 return new HttpTransportPipe(this,cloner);
ohair@286 412 }
ohair@286 413
ohair@286 414
ohair@286 415 private void dump(ByteArrayBuffer buf, String caption, Map<String, List<String>> headers) throws IOException {
ohair@286 416 ByteArrayOutputStream baos = new ByteArrayOutputStream();
ohair@286 417 PrintWriter pw = new PrintWriter(baos, true);
ohair@286 418 pw.println("---["+caption +"]---");
ohair@286 419 for (Entry<String,List<String>> header : headers.entrySet()) {
ohair@286 420 if(header.getValue().isEmpty()) {
ohair@286 421 // I don't think this is legal, but let's just dump it,
ohair@286 422 // as the point of the dump is to uncover problems.
ohair@286 423 pw.println(header.getValue());
ohair@286 424 } else {
ohair@286 425 for (String value : header.getValue()) {
ohair@286 426 pw.println(header.getKey()+": "+value);
ohair@286 427 }
ohair@286 428 }
ohair@286 429 }
ohair@286 430
mkos@408 431 if (buf.size() > HttpAdapter.dump_threshold) {
mkos@408 432 byte[] b = buf.getRawData();
mkos@408 433 baos.write(b, 0, HttpAdapter.dump_threshold);
mkos@408 434 pw.println();
mkos@408 435 pw.println(WsservletMessages.MESSAGE_TOO_LONG(HttpAdapter.class.getName() + ".dumpTreshold"));
mkos@408 436 } else {
mkos@408 437 buf.writeTo(baos);
mkos@408 438 }
ohair@286 439 pw.println("--------------------");
ohair@286 440
ohair@286 441 String msg = baos.toString();
ohair@286 442 if (dump) {
alanb@368 443 System.out.println(msg);
ohair@286 444 }
ohair@286 445 if (LOGGER.isLoggable(Level.FINER)) {
alanb@368 446 LOGGER.log(Level.FINER, msg);
ohair@286 447 }
ohair@286 448 }
ohair@286 449
ohair@286 450 }

mercurial