/* Copyright (c) 2008 Google Inc.
*
* 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 com.google.gdata.util;
import com.google.gdata.util.ErrorContent.LocationType;
import com.google.gdata.util.XmlParser.ElementHandler;
import org.xml.sax.Attributes;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.net.HttpURLConnection;
import java.util.Collections;
import java.util.zip.GZIPInputStream;
/**
* The ServiceExceptionInitializer class is used to initialize
* a ServiceException from either an HTTP connection or an
* XML structured error represented as a string.
*
*
*
*
*/
public class ServiceExceptionInitializer {
// Set to the exception we begin by initializing;
// initialized when this object is constructed.
// WARNING: This object may not be fully constructed.
// We set its instance variables, but you MUST NOT
// invoke any instance methods on it.
private final ServiceException initialException;
// Set to the exception we are filling during parsing.
// WARNING: This object may not be fully constructed.
// We set its instance variables, but you MUST NOT
// invoke any instance methods on it.
private ServiceException currentException = null;
// Constructor
public ServiceExceptionInitializer(ServiceException se) {
initialException = se;
}
/**
* Initializes the ServiceException using the error response data from an
* HTTP connection. This constructor is used in the client library
* to approximately reconstitute the constructor as it appeared
* in the server.
* @param httpConn is the http connection from which the error message
* (structured or simple) is read
* @throws IOException if network error receiving the error response
*/
public void parse(HttpURLConnection httpConn)
throws ParseException, IOException {
// Save response code
initialException.httpErrorCodeOverride = httpConn.getResponseCode();
// Save the HTTP headers in the exception.
initialException.httpHeaders =
Collections.unmodifiableMap(httpConn.getHeaderFields());
// Save the response content type and body in the exception also
ContentType responseContentType =
new ContentType(httpConn.getContentType());
initialException.responseContentType = responseContentType;
StringBuilder sb;
int responseLength = httpConn.getContentLength();
if (responseLength < 0) {
sb = new StringBuilder();
} else if (responseLength > 0) {
sb = new StringBuilder(responseLength);
} else { // no response data
return;
}
// read an arbitrary response stream.
InputStream responseStream =
(initialException.httpErrorCodeOverride >= 400)
? httpConn.getErrorStream() : httpConn.getInputStream();
if (responseStream != null) {
if ("gzip".equalsIgnoreCase(httpConn.getContentEncoding())) {
responseStream = new GZIPInputStream(responseStream);
}
try {
String charset = responseContentType.getAttributes().get("charset");
if (charset == null) {
charset = "iso8859-1"; // http default encoding
}
BufferedReader reader =
new BufferedReader(new InputStreamReader(responseStream, charset));
String responseLine;
while ((responseLine = reader.readLine()) != null) {
sb.append(responseLine);
sb.append('\n');
}
String body = sb.toString();
initialException.responseBody = body;
parse(responseContentType, body);
} finally {
responseStream.close();
}
}
}
public void parse(ContentType contentType, String body)
throws ParseException {
if (ContentType.GDATA_ERROR.equals(contentType)) {
XmlParser xp = new XmlParser();
try {
xp.parse(new StringReader(body), new ErrorsHandler(),
"http://schemas.google.com/g/2005", "errors");
} catch (IOException ioe) {
// Impossible case: we are always parsing from a String
throw new RuntimeException("Impossible parser I/O", ioe);
}
}
}
/**
* Creates a new exception, registers it and sets {@code currentException}.
* The first time this method is called, {@code currentException} is set
* to {@code initialException}.
* @return the new value of {@code currentException}
*/
private ServiceException nextException() {
// nextException/currentException
if (currentException == null) {
currentException = initialException;
return currentException;
}
try {
// Create an exception of the same type as this one
// and make it a sibling.
currentException = initialException.getClass()
.getConstructor(String.class).newInstance(
initialException.getMessage());
initialException.addSibling(currentException);
return currentException;
} catch (InstantiationException ie) {
throw new IllegalStateException(ie);
} catch (NoSuchMethodException nsme) {
throw new IllegalStateException(nsme);
} catch (IllegalAccessException iae) {
throw new IllegalStateException(iae);
} catch (InvocationTargetException ite) {
throw new IllegalStateException(ite);
}
}
// ElementHandler subclasses for client-side parsing.
private class ErrorsHandler extends ElementHandler {
@Override
public ElementHandler getChildHandler(String namespace, String localName,
Attributes attrs) throws ParseException, IOException {
if (Namespaces.g.equals(namespace)) {
if ("error".equals(localName)) {
nextException();
return new ErrorHandler();
}
}
return super.getChildHandler(namespace, localName, attrs);
}
}
private class ErrorHandler extends ElementHandler {
@Override
public ElementHandler getChildHandler(String namespace, String localName,
Attributes attrs) throws ParseException, IOException {
if ("http://schemas.google.com/g/2005".equals(namespace)) {
if ("domain".equals(localName)) {
return new DomainHandler();
} else if ("code".equals(localName)) {
return new CodeHandler();
} else if ("location".equals(localName)) {
return new LocationHandler();
} else if ("internalReason".equals(localName)) {
return new InternalReasonHandler();
} else if ("extendedHelp".equals(localName)) {
return new ExtendedHelpHandler();
} else if ("sendReport".equals(localName)) {
return new SendReportHandler();
} else if ("debugInfo".equals(localName)) {
return new DebugInfoHandler();
}
}
return super.getChildHandler(namespace, localName, attrs);
}
}
private class DomainHandler extends ElementHandler {
@Override public void processEndElement() {
currentException.errorElement.setDomain(value);
}
}
private class CodeHandler extends ElementHandler {
@Override public void processEndElement() {
currentException.errorElement.setCode(value);
}
}
private class LocationHandler extends ElementHandler {
private LocationType locationType = LocationType.OTHER;
@Override
public void processAttribute(String namespace, String localName,
String value) {
if ("type".equals(localName)) {
locationType = LocationType.fromString(value);
}
}
@Override
public void processEndElement() {
currentException.errorElement.setLocation(value, locationType);
}
}
private class InternalReasonHandler extends ElementHandler {
@Override public void processEndElement() {
currentException.errorElement.setInternalReason(value);
}
}
private class ExtendedHelpHandler extends ElementHandler {
@Override public void processEndElement() {
currentException.errorElement.setExtendedHelp(value);
}
}
private class SendReportHandler extends ElementHandler {
@Override public void processEndElement() {
currentException.errorElement.setSendReport(value);
}
}
private class DebugInfoHandler extends ElementHandler {
@Override public void processEndElement() {
currentException.errorElement.setDebugInfo(value);
}
}
}