org.expath.httpclient.impl.BodyFactory Maven / Gradle / Ivy
Show all versions of http-client-java Show documentation
/****************************************************************************/
/* File: BodyFactory.java */
/* Author: F. Georges - fgeorges.org */
/* Date: 2009-02-03 */
/* Tags: */
/* Copyright (c) 2009 Florent Georges (see end of file.) */
/* ------------------------------------------------------------------------ */
package org.expath.httpclient.impl;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Set;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.message.BasicHeader;
import org.expath.httpclient.*;
import org.expath.httpclient.model.Result;
import org.expath.tools.model.Element;
import org.expath.tools.model.Sequence;
import javax.annotation.Nullable;
/**
* Factory class for bodies, either in requests or in responses.
*
* @author Florent Georges
*/
public class BodyFactory {
// TODO: Take new methods into account (XHTML, BASE64 and HEX).
public static HttpRequestBody makeRequestBody(final Element elem, final Sequence bodies, final String ns)
throws HttpClientException {
// method is got from @method if any...
Type method = parseMethod(elem);
// ...or from @media-type if no @method
if (method == null) {
method = parseType(elem);
}
switch (method) {
case MULTIPART:
return new MultipartRequestBody(elem, bodies, ns);
case SRC:
return new HrefRequestBody(elem);
case XML:
case HTML:
case XHTML:
case TEXT:
case BINARY:
case HEX:
case BASE64:
return new SinglePartRequestBody(elem, bodies, method);
default:
throw new HttpClientException(HttpClientError.HC001, "could not happen");
}
}
public static HttpResponseBody makeResponseBody(final Result result, final ContentType type, final HttpConnection conn)
throws HttpClientException {
if (type == null) {
// it is legitimate to not have a body in a response; for instance
// on a "304 Not Modified"
return null;
}
String t = type.getType();
if (t == null) {
return null;
}
final InputStream in = conn.getResponseStream();
if (in == null) {
return null;
}
if (t.startsWith("multipart/")) {
return new MultipartResponseBody(result, in, type);
} else {
return makeResponsePart(result, null, in, type);
}
}
// package-level to be used within MultipartResponseBody ctor
// TODO: Take new methods into account (XHTML, BASE64 and HEX).
static HttpResponseBody makeResponsePart(final Result result, final HeaderSet headers, final InputStream in, final ContentType ctype)
throws HttpClientException {
switch (parseType(ctype)) {
case XML:
// TODO: 'content_type' is the header Content-Type without any param
// (i.e. "text/xml".) Should we keep this, or put the whole header
// (i.e. "text/xml; charset=utf-8")? (and for other types as well...)
return new XmlResponseBody(result, in, ctype, headers, false);
case HTML:
return new XmlResponseBody(result, in, ctype, headers, true);
case TEXT:
return new TextResponseBody(result, in, ctype, headers);
case BINARY:
return new BinaryResponseBody(result, in, ctype, headers);
default:
throw new HttpClientException(HttpClientError.HC001, "INTERNAL ERROR: cannot happen");
}
}
public enum Type {
XML,
HTML,
XHTML,
TEXT,
BINARY,
BASE64,
HEX,
MULTIPART,
SRC
}
/**
* Media types that must be treated as text types (in addition to text/*).
*/
private static final Set TEXT_TYPES = new HashSet<>();
static {
TEXT_TYPES.add("application/x-www-form-urlencoded");
TEXT_TYPES.add("application/xml-dtd");
}
/**
* Media types that must be treated as XML types (in addition to *+xml).
*/
private static final Set XML_TYPES = new HashSet<>();
static {
// Doc: does not handle "application/xml-dtd" as XML
// TODO: What about ".../xml-external-parsed-entity" ?
XML_TYPES.add("text/xml");
XML_TYPES.add("application/xml");
XML_TYPES.add("text/xml-external-parsed-entity");
XML_TYPES.add("application/xml-external-parsed-entity");
}
/**
* Decode the content type from a MIME type string.
*
* TODO: Take new methods into account (XHTML, BASE64 and HEX).
*/
private static Type parseType(final String type) {
if (type.startsWith("multipart/")) {
return Type.MULTIPART;
} else if ("text/html".equals(type)) {
return Type.HTML;
} else if (type.endsWith("+xml") || XML_TYPES.contains(type)) {
return Type.XML;
} else if (type.startsWith("text/") || TEXT_TYPES.contains(type)) {
return Type.TEXT;
} else {
return Type.BINARY;
}
}
/**
* Look for the header Content-Type in a header set and decode it.
*
* @param headers the headers to parse the Content-Type from
* @return the Content-Type representation
* @throws HttpClientException if the headers are null
*/
public static Type parseType(final HeaderSet headers) throws HttpClientException {
final Header h = headers.getFirstHeader("Content-Type");
if (h == null) {
throw new HttpClientException(HttpClientError.HC001, "impossible to find the content type");
}
final ContentType ct = ContentType.parse(h, null, null);
return parseType(ct);
}
/**
* Decode the content type from a ContentType object.
*
* @param type the content type
* @return the Content-Type representation
* @throws HttpClientException if the headers are null, or the content type cannot be found
*/
public static Type parseType(@Nullable final ContentType type) throws HttpClientException {
if (type == null) {
throw new HttpClientException(HttpClientError.HC001, "impossible to find the content type");
}
final String t = type.getType();
if (t == null) {
throw new HttpClientException(HttpClientError.HC001, "impossible to find the content type");
}
return parseType(t);
}
/**
* Parse the @media-type from a http:body or http:multipart element.
*/
private static Type parseType(final Element elem) throws HttpClientException {
final String local = elem.getLocalName();
if ("multipart".equals(local)) {
return Type.MULTIPART;
} else if (!"body".equals(local)) {
throw new HttpClientException(HttpClientError.HC001, "INTERNAL ERROR: cannot happen, checked before");
} else {
if (elem.getAttribute("src") != null) {
return Type.SRC;
}
final String mediaType = elem.getAttribute("media-type");
if (mediaType == null) {
throw new HttpClientException(HttpClientError.HC001, "@media-type is not set on http:body");
}
final Header mediaTypeHeader = new BasicHeader("Media-Type", mediaType);
final HeaderElement[] mediaTypeHeaderElems = mediaTypeHeader.getElements();
if (mediaTypeHeaderElems == null || mediaTypeHeaderElems.length == 0) {
throw new HttpClientException(HttpClientError.HC001, "@media-type is not set on http:body");
} else if (mediaTypeHeaderElems.length > 1) {
throw new HttpClientException(HttpClientError.HC001, "Multiple @media-type internet media types present");
} else {
final Type type = parseType(mediaTypeHeaderElems[0].getName());
if (type == Type.MULTIPART) {
final String msg = "multipart type not allowed for http:body: " + mediaType;
throw new HttpClientException(HttpClientError.HC001, msg);
}
return type;
}
}
}
/**
* Parse the @method from a http:body or http:multipart element.
*
* Return null if there is no @method.
*/
private static Type parseMethod(final Element elem) throws HttpClientException {
final String m = elem.getAttribute("method");
if (m == null) {
return null;
} else if ("xml".equals(m)) {
return Type.XML;
} else if ("html".equals(m)) {
return Type.HTML;
} else if ("xhtml".equals(m)) {
return Type.XHTML;
} else if ("text".equals(m)) {
return Type.TEXT;
} else if ("binary".equals(m)) {
return Type.BINARY;
}
// FIXME: The spec says "binary", but I think we need "base64" and "hex"
// instead (or in addition, if "binary" is left implementation-defined).
else if ("base64".equals(m)) {
return Type.BASE64;
} else if ("hex".equals(m)) {
return Type.HEX;
} else {
throw new HttpClientException(HttpClientError.HC001, "Incorrect value for @method: " + m);
}
}
}
/* ------------------------------------------------------------------------ */
/* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS COMMENT. */
/* */
/* The contents of this file are subject to the Mozilla Public License */
/* Version 1.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.mozilla.org/MPL/. */
/* */
/* Software distributed under the License is distributed on an "AS IS" */
/* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See */
/* the License for the specific language governing rights and limitations */
/* under the License. */
/* */
/* The Original Code is: all this file. */
/* */
/* The Initial Developer of the Original Code is Florent Georges. */
/* */
/* Contributor(s): none. */
/* ------------------------------------------------------------------------ */