/* 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.wireformats;
import com.google.gdata.util.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.gdata.util.ContentType;
import java.util.List;
import java.util.Set;
/**
* The AltFormat class represents an alternate representation format for a GData
* resources. An alternate format has a name (which may be used as the {@code
* alt} query parameter for GData requests), a primary MIME content type (which
* may act as an alias for the name if unique to the representation), and
* several other attributes.
* <p>
* Two AltFormat instances are considered to be equal if they have the same
* name.
* <p>
* This class also exposes static constants for common GData alternate
* representation formats that are available across multiple GData services.
*
*
*/
public class AltFormat {
/**
* Constant value representing the Really Simple Syndication (RSS) format, as
* defined by <a href="http://cyber.law.harvard.edu/rss/rss.html">RSS 2.0
* </a>.
*/
public static final AltFormat RSS = builder()
.setName("rss")
.setWireFormat(WireFormat.XML)
.setContentType(ContentType.RSS)
.setAcceptableXmlTypes()
.setSelectableByType(true)
.build();
/**
* Constant value representing the OpenSearch Description Document, as
* described by <a href="http://www.opensearch.org/Home">OpenSearch 1.1</a>.
* This representation is only able for feed resources.
*/
public static final AltFormat OPENSEARCH = builder()
.setName("opensearch")
.setWireFormat(WireFormat.XML)
.setContentType(ContentType.OPENSEARCH)
.setAcceptableXmlTypes()
.setSelectableByType(true)
.build();
/**
* Constant value representing the AtomPub Service Document, as described
* by <a href="http://www.ietf.org/rfc/rfc5023.txt">RFC 5023</a>. This
* representation is only available for feed resources.
*/
public static final AltFormat ATOM_SERVICE = builder()
.setName("atom-service")
.setWireFormat(WireFormat.XML)
.setContentType(ContentType.ATOM_SERVICE)
.setAcceptableXmlTypes()
.setSelectableByType(true)
.build();
/**
* Constant value representing application/xml document
*/
public static final AltFormat APPLICATION_XML = builder()
.setName("application-xml")
.setWireFormat(WireFormat.XML)
.setContentType(ContentType.APPLICATION_XML)
.setAcceptableXmlTypes()
.setSelectableByType(true)
.build();
/**
* Constant value representing the media content associated with a GData
* resource. The actual content type returned will depend upon the native
* media representation of the target resource.
*/
public static final AltFormat MEDIA = builder()
.setName("media")
.setContentType(ContentType.ANY)
.build();
/**
* Constant value representing the MIME Multipart Document format, as
* described by <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>.
* The multipart document contains both the Atom representation (as one part)
* and the associated media content (as another part). This representation is
* available only for GData media resources.
*/
public static final AltFormat MEDIA_MULTIPART = builder()
.setName("media-multipart")
.setContentType(ContentType.MULTIPART_RELATED)
.setSelectableByType(true)
.build();
/**
* Constant value representing the Atom Syndication Format, as defined by
* <a href="http://www.ietf.org/rfc/rfc4287.txt">RFC 4287</a>.
*/
public static final AltFormat ATOM = builder()
.setName("atom")
.setWireFormat(WireFormat.XML)
.setContentType(ContentType.ATOM)
.setAcceptableXmlTypes()
.addAllowedInputFormats(MEDIA, MEDIA_MULTIPART, APPLICATION_XML)
.setSelectableByType(true)
.build();
/**
* Creates a builder for a new AltFormat.
*/
public static Builder builder() {
return new Builder();
}
/**
* Creates a builder for a new AltFormat. Pre-initializes the builder with a
* prototype format.
*/
public static Builder builder(AltFormat format) {
return new Builder(format);
}
/** Formal name of alternate representation */
private final String name;
/**
* Wire format to use when parsing or generating the class (or {@code null}
* if no associated wire format exists).
*/
private final WireFormat wireFormat;
/** Primary MIME content type for alternate representation */
private final ContentType contentType;
/** List of acceptable matching types for alternate representation */
private final List<ContentType> acceptList;
/**
* Boolean used to indicate that the primary MIME type can be used to select
* the representation.
*/
private final boolean isSelectableByType;
private final Set<AltFormat> extraInputFormats;
/**
* The base alt format this alt format wraps another one, for the *-in-script
* variants.
*/
private final AltFormat base;
private AltFormat(Builder builder) {
base = builder.base;
name = builder.name;
wireFormat = builder.wireFormat;
contentType = builder.contentType;
ImmutableList.Builder<ContentType> acceptListBuilder =
ImmutableList.builder();
acceptListBuilder.add(contentType);
if (builder.acceptableTypes != null) {
acceptListBuilder.addAll(builder.acceptableTypes);
}
acceptList = acceptListBuilder.build();
isSelectableByType = builder.isSelectableByType;
extraInputFormats = builder.extraInputFormats.build();
}
/**
* Constructs a new alternate representation format with the provided
* attributes.
*
* @param name the short name for this format. This values is suitable for use
* as the value of the {@code alt} query parameter.
* @param wireFormat the content wire format or {@code null} if there is no
* associated wire format for the representation.
* @param contentType the primary MIME content type used for the
* representation.
* @param acceptList a list of all MIME types that will be considered to
* potentially match the representation for the purposes of content
* negotiation. A value of {@code null} is equivalent to a single item
* list containing only the primary content type.
* @param isSelectableByType if {@code true}, indicates that the MIME content
* type can be used as an alias to select the representation.
* @deprecated Please use the {@link Builder} instead. See {@link #builder()}.
*/
@Deprecated
public AltFormat(String name, WireFormat wireFormat, ContentType contentType,
List<ContentType> acceptList, boolean isSelectableByType) {
this(builder()
.setName(name)
.setWireFormat(wireFormat)
.setContentType(contentType)
.setAcceptableTypes(acceptList)
.setSelectableByType(isSelectableByType)
.check());
}
/**
* Returns the short name for this format. This values is suitable for use as
* the value of the {@code alt} query parameter.
*/
public String getName() {
return name;
}
/**
* Returns the {@link WireFormat} that is used to parse and generate the
* representation or {@code null} if no supporting wire format exists.
*/
public WireFormat getWireFormat() {
return wireFormat;
}
/**
* Returns the primary MIME content type used for the representation.
*/
public ContentType getContentType() {
return contentType;
}
/**
* Returns a list of all MIME types that will be considered to potentially
* match the representation for the purposes of content negotiation.
*/
public List<ContentType> getMatchingTypes() {
return acceptList;
}
/**
* Returns {@code true}, indicates that the MIME content type can be used as
* an alias to select the representation.
*/
public boolean isSelectableByType() {
return isSelectableByType;
}
/**
* Returns {@code true} if {@code inputFormat} is allowed as input format when
* this format with this format as the output format.
*/
public boolean allowInputFormat(AltFormat inputFormat) {
return inputFormat == this || extraInputFormats.contains(inputFormat);
}
/**
* Returns {@code true} if this format is a variant, such as *-in-script.
*/
public boolean hasBaseFormat() {
return base != null;
}
/**
* Returns the base format, if this format is a variant. Otherwise returns
* {@code null}.
*/
public AltFormat getBaseFormat() {
return base;
}
@Override
public boolean equals(Object o) {
return o == this
|| (o instanceof AltFormat && name.equals(((AltFormat) o).name));
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public String toString() {
return name + "[" + contentType + "]";
}
/**
* A builder for AltFormat. Create new builders using {@link
* AltFormat#builder()} or {@link AltFormat#builder(AltFormat)}.
*/
public static class Builder {
private String name;
private WireFormat wireFormat;
private ContentType contentType;
private Set<ContentType> acceptableTypes;
private final ImmutableSet.Builder<AltFormat> extraInputFormats =
ImmutableSet.<AltFormat>builder();
private boolean isSelectableByType;
private AltFormat base;
private Builder() {
}
private Builder(AltFormat prototype) {
name = prototype.name;
wireFormat = prototype.wireFormat;
contentType = prototype.contentType;
acceptableTypes = ImmutableSet.copyOf(prototype.acceptList);
extraInputFormats.addAll(prototype.extraInputFormats);
isSelectableByType = prototype.isSelectableByType;
base = prototype.base;
}
/**
* Sets the short name for this format. This value is suitable for
* use as the value of the {@code alt} query parameter. This is
* required.
*/
public Builder setName(String name) {
this.name = name;
return this;
}
/**
* Sets the content wire format.
*/
public Builder setWireFormat(WireFormat wireFormat) {
this.wireFormat = wireFormat;
return this;
}
/**
* Sets the primary MIME content type used for the representation.
* This is the content type expected as input and the default content
* type returned as output.
*/
public Builder setContentType(ContentType contentType) {
this.contentType = contentType;
return this;
}
/**
* Indicates whether the MIME content type can be used as an alias to select
* the representation.
*/
public Builder setSelectableByType(boolean isSelectableByType) {
this.isSelectableByType = isSelectableByType;
return this;
}
/**
* Declares another {@code AltFormat} as the base for this one.
*
* <p>This is set for *-in-script variants and to allows access to the
* original format.
*/
public Builder setBaseFormat(AltFormat base) {
this.base = base;
return this;
}
/**
* Declares text/plain to be an acceptable match for the purpose
* of content negotiation.
*/
public Builder setAcceptableTextTypes() {
return setAcceptableTypes(ContentType.TEXT_PLAIN);
}
/**
* Declares text/xml and text/plain to be acceptable matches for the
* purpose of content negotiation.
*/
public Builder setAcceptableXmlTypes() {
return setAcceptableTypes(ContentType.TEXT_XML, ContentType.TEXT_PLAIN);
}
/**
* Declares MIME types to be acceptable matches for the purpose
* of content negotiation. By default, only the content type set
* using {@link #setContentType} is acceptable.
*/
public Builder setAcceptableTypes(ContentType... types) {
if (types == null) {
acceptableTypes = ImmutableSet.of();
} else {
acceptableTypes = ImmutableSet.copyOf(types);
}
return this;
}
/**
* Declares MIME types to be acceptable matches for the purpose
* of content negotiation. By default, only the content type set
* using {@link #setContentType} is acceptable.
*/
private Builder setAcceptableTypes(Iterable<ContentType> types) {
if (types == null) {
acceptableTypes = ImmutableSet.of();
} else {
acceptableTypes = ImmutableSet.copyOf(types);
}
return this;
}
/**
* Declares formats as being acceptable input formats when the
* current AltFormat is selected as output.
*
* <p>When posting or putting data, the request content type is
* expected to be the same as the response content type selected
* with the alt query parameter. This is enforced in the server.
*
* <p>For example, this is acceptable:
* <pre>
* POST http://gdata.example.com/feeds/myfeed?alt=jsonc
* ContentType: application/json
* </pre>
* and this is not:
* <pre>
* POST http://gdata.example.com/feeds/myfeed?alt=jsonc
* ContentType: application/atom+xml
* </pre>
*
* <p>Some cases violate this rule. For example, when posting a media
* file, the request content type is the type of the media while
* the response content type is either atom or json.
*
* <p>Such special cases should be declared using this method.
*/
public Builder addAllowedInputFormats(AltFormat... formats) {
for (AltFormat format : formats) {
extraInputFormats.add(format);
}
return this;
}
/**
* Builds the {@link AltFormat}. This can be called only once.
*
* @throws IllegalStateException unless both name and content type are set
*/
public AltFormat build() {
check();
return new AltFormat(this);
}
private Builder check() {
Preconditions.checkState(name != null, "Name must be set");
Preconditions.checkState(contentType != null, "contentType must be set");
return this;
}
}
}