blob: dfdc31f1e7c754ac796d61396aa1b5f19bfb6b3f [file] [log] [blame]
// Copyright 2008 The Android Open Source Project
package com.google.wireless.gdata2.client;
import com.google.wireless.gdata2.ConflictDetectedException;
import com.google.wireless.gdata2.client.AuthenticationException;
import com.google.wireless.gdata2.client.HttpException;
import com.google.wireless.gdata2.client.ResourceGoneException;
import com.google.wireless.gdata2.client.ResourceNotFoundException;
import com.google.wireless.gdata2.data.Entry;
import com.google.wireless.gdata2.data.MediaEntry;
import com.google.wireless.gdata2.data.StringUtils;
import com.google.wireless.gdata2.parser.GDataParser;
import com.google.wireless.gdata2.parser.ParseException;
import com.google.wireless.gdata2.serializer.GDataSerializer;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
/**
* Abstract base class for service-specific clients to access GData feeds.
*/
public abstract class GDataServiceClient {
/**
* Default gdata protocol version that this library supports
*/
protected static String DEFAULT_GDATA_VERSION = "2.0";
private final GDataClient gDataClient;
private final GDataParserFactory gDataParserFactory;
public GDataServiceClient(GDataClient gDataClient, GDataParserFactory gDataParserFactory) {
this.gDataClient = gDataClient;
this.gDataParserFactory = gDataParserFactory;
}
/**
* Returns the {@link GDataClient} being used by this GDataServiceClient.
*
* @return The {@link GDataClient} being used by this GDataServiceClient.
*/
protected GDataClient getGDataClient() {
return gDataClient;
}
/**
* Returns the protocol version used by this GDataServiceClient, in the form
* of a "2.1" string
*
* @return String
*/
public abstract String getProtocolVersion();
/**
* Returns the {@link GDataParserFactory} being used by this
* GDataServiceClient.
*
* @return The {@link GDataParserFactory} being used by this
* GDataServiceClient.
*/
protected GDataParserFactory getGDataParserFactory() {
return gDataParserFactory;
}
/**
* Returns the name of the service. Used for authentication.
*
* @return The name of the service.
*/
public abstract String getServiceName();
/**
* Creates {@link QueryParams} that can be used to restrict the feed contents
* that are fetched.
*
* @return The QueryParams that can be used with this client.
*/
public QueryParams createQueryParams() {
return gDataClient.createQueryParams();
}
/**
* Fetches a feed for this user. The caller is responsible for closing the
* returned {@link GDataParser}.
*
* @param feedEntryClass the class of Entry that is contained in the feed
* @param feedUrl ThAe URL of the feed that should be fetched.
* @param authToken The authentication token for this user.
* @param eTag The etag used for this query. Passing null will result in an
* unconditional query
* @return A {@link GDataParser} for the requested feed.
* @throws AuthenticationException Thrown if the server considers the
* authToken invalid.
* @throws ResourceGoneException Thrown if the server indicates that the
* resource is Gone. Currently used to indicate that some Tombstones
* are missing.
* @throws ResourceNotModifiedException Thrown if the retrieval fails because
* the specified ETag matches the current ETag of the entry (i.e. the
* entry has not been modified since last retrieval).
* @throws HttpException Thrown if the request returns an error response.
* @throws ParseException Thrown if the server response cannot be parsed.
* @throws IOException Thrown if an error occurs while communicating with the
* GData service.
*/
public GDataParser getParserForFeed(Class feedEntryClass, String feedUrl, String authToken,
String eTag) throws AuthenticationException, ResourceGoneException,
ResourceNotModifiedException, HttpException, ParseException, IOException,
ForbiddenException {
try {
InputStream is = gDataClient.getFeedAsStream(feedUrl, authToken, eTag, getProtocolVersion());
return gDataParserFactory.createParser(feedEntryClass, is);
} catch (HttpException e) {
convertHttpExceptionForFeedReads("Could not fetch feed " + feedUrl, e);
return null; // never reached
}
}
/**
* Fetches a media entry as an InputStream. The caller is responsible for
* closing the returned {@link InputStream}.
*
* @param mediaEntryUrl The URL of the media entry that should be fetched.
* @param authToken The authentication token for this user.
* @param eTag The ETag associated with this request.
* @return A {@link InputStream} for the requested media entry.
* @throws AuthenticationException Thrown if the server considers the
* authToken invalid.
* @throws ResourceGoneException Thrown if the server indicates that the
* resource is Gone. Currently used to indicate that some Tombstones
* are missing.
* @throws ResourceNotModifiedException Thrown if the retrieval fails because
* the specified ETag matches the current ETag of the entry (i.e. the
* entry has not been modified since last retrieval).
* @throws ResourceNotFoundException Thrown if the resource was not found.
* @throws HttpException Thrown if the request returns an error response.
* @throws IOException Thrown if an error occurs while communicating with the
* GData service.
*/
public InputStream getMediaEntryAsStream(String mediaEntryUrl, String authToken, String eTag)
throws AuthenticationException, ResourceGoneException, ResourceNotModifiedException,
ResourceNotFoundException, HttpException, IOException, ForbiddenException {
try {
return gDataClient
.getMediaEntryAsStream(mediaEntryUrl, authToken, eTag, getProtocolVersion());
} catch (HttpException e) {
convertHttpExceptionForEntryReads("Could not fetch media entry " + mediaEntryUrl, e);
return null; // never reached
}
}
/**
* Creates a new entry at the provided feed. Parses the server response into
* the version of the entry stored on the server.
*
* @param feedUrl The feed where the entry should be created.
* @param authToken The authentication token for this user.
* @param entry The entry that should be created.
* @return The entry returned by the server as a result of creating the
* provided entry.
* @throws ConflictDetectedException Thrown if the server detects an existing
* entry that conflicts with this one.
* @throws AuthenticationException Thrown if the server considers the
* authToken invalid.
* @throws PreconditionFailedException Thrown if the update fails because the
* specified ETag does not match the current ETag of the
* @throws HttpException Thrown if the request returns an error response.
* @throws ParseException Thrown if the server response cannot be parsed.
* @throws IOException Thrown if an error occurs while communicating with the
* GData service.
*/
public Entry createEntry(String feedUrl, String authToken, Entry entry)
throws ConflictDetectedException, AuthenticationException, PreconditionFailedException,
HttpException, ParseException, IOException, ForbiddenException {
GDataSerializer serializer = gDataParserFactory.createSerializer(entry);
try {
InputStream is =
gDataClient.createEntry(feedUrl, authToken, getProtocolVersion(), serializer);
return parseEntry(entry.getClass(), is);
} catch (HttpException e) {
try {
convertHttpExceptionForWrites(entry.getClass(),
"Could not create " + "entry " + feedUrl, e);
} catch (ResourceNotFoundException e1) {
// this should never happen
throw e;
}
return null; // never reached.
}
}
/**
* Fetches an existing entry.
*
* @param entryClass the type of entry to expect
* @param id of the entry to fetch.
* @param authToken The authentication token for this user
* @param eTag The etag used for this query. Passing null will result in an
* unconditional query
* @return The entry returned by the server.
* @throws AuthenticationException Thrown if the server considers the
* authToken invalid.
* @throws ResourceNotFoundException Thrown if the resource was not found.
* @throws ResourceNotModifiedException Thrown if the retrieval fails because
* the specified ETag matches the current ETag of the entry (i.e. the
* entry has not been modified since last retrieval).
* @throws HttpException Thrown if the request returns an error response.
* @throws ParseException Thrown if the server response cannot be parsed.
* @throws IOException Thrown if an error occurs while communicating with the
* GData service.
*/
public Entry getEntry(Class entryClass, String id, String authToken, String eTag)
throws AuthenticationException, ResourceNotFoundException, ResourceNotModifiedException,
HttpException, ParseException, IOException, ForbiddenException {
try {
InputStream is = getGDataClient().getFeedAsStream(id, authToken, eTag, getProtocolVersion());
return parseEntry(entryClass, is);
} catch (HttpException e) {
convertHttpExceptionForEntryReads("Could not fetch entry " + id, e);
return null; // never reached
}
}
/**
* Updates an existing entry. Parses the server response into the version of
* the entry stored on the server.
*
* @param entry The entry that should be updated.
* @param authToken The authentication token for this user.
* @return The entry returned by the server as a result of updating the
* provided entry.
* @throws AuthenticationException Thrown if the server considers the
* authToken invalid.
* @throws ConflictDetectedException Thrown if the server detects an existing
* entry that conflicts with this one, or if the server version of
* this entry has changed since it was retrieved.
* @throws PreconditionFailedException Thrown if the update fails because the
* specified ETag does not match the current ETag of the entry.
* @throws HttpException Thrown if the request returns an error response.
* @throws ParseException Thrown if the server response cannot be parsed.
* @throws IOException Thrown if an error occurs while communicating with the
* GData service.
*/
public Entry updateEntry(Entry entry, String authToken) throws AuthenticationException,
ConflictDetectedException, PreconditionFailedException, HttpException, ParseException,
IOException, ForbiddenException, ResourceNotFoundException {
String editUri = entry.getEditUri();
if (StringUtils.isEmpty(editUri)) {
throw new ParseException("No edit URI -- cannot update.");
}
GDataSerializer serializer = gDataParserFactory.createSerializer(entry);
try {
InputStream is =
gDataClient.updateEntry(editUri, authToken, entry.getETag(), getProtocolVersion(),
serializer);
return parseEntry(entry.getClass(), is);
} catch (HttpException e) {
convertHttpExceptionForWrites(entry.getClass(), "Could not update " + "entry " + editUri, e);
return null; // never reached
}
}
/**
* Updates an existing entry. Parses the server response into the metadata of
* the entry stored on the server.
*
* @param editUri The URI of the resource that should be updated.
* @param inputStream The {@link java.io.InputStream} that contains the new
* value of the media entry
* @param contentType The content type of the new media entry
* @param authToken The authentication token for this user.
* @return The entry returned by the server as a result of updating the
* provided entry
* @param eTag The etag used for this query. Passing null will result in an
* unconditional query
* @throws AuthenticationException Thrown if the server considers the
* authToken invalid.
* @throws ConflictDetectedException Thrown if the server detects an existing
* entry that conflicts with this one, or if the server version of
* this entry has changed since it was retrieved.
* @throws PreconditionFailedException Thrown if the update fails because the
* specified ETag does not match the current ETag of the entry.
* @throws HttpException Thrown if the request returns an error response.
* @throws ParseException Thrown if the server response cannot be parsed.
* @throws IOException Thrown if an error occurs while communicating with the
* GData service.
*/
public MediaEntry updateMediaEntry(String editUri, InputStream inputStream, String contentType,
String authToken, String eTag) throws AuthenticationException, ConflictDetectedException,
PreconditionFailedException, HttpException, ParseException, IOException,
ForbiddenException, ResourceNotFoundException {
if (StringUtils.isEmpty(editUri)) {
throw new IllegalArgumentException("No edit URI -- cannot update.");
}
try {
InputStream is =
gDataClient.updateMediaEntry(editUri, authToken, eTag, getProtocolVersion(), inputStream,
contentType);
return (MediaEntry) parseEntry(MediaEntry.class, is);
} catch (HttpException e) {
convertHttpExceptionForWrites(MediaEntry.class, "Could not update entry " + editUri, e);
return null; // never reached
}
}
/**
* Deletes an existing entry.
*
* @param editUri The editUri for the entry that should be deleted.
* @param authToken The authentication token for this user.
* @param eTag The etag used for this query. Passing null will result in an
* unconditional query
* @throws AuthenticationException Thrown if the server considers the
* authToken invalid.
* @throws ConflictDetectedException Thrown if the server version of this
* entry has changed since it was retrieved.
* @throws PreconditionFailedException Thrown if the update fails because the
* specified ETag does not match the current ETag of the entry.
* @throws HttpException Thrown if the request returns an error response.
* @throws ParseException Thrown if the server response cannot be parsed.
* @throws IOException Thrown if an error occurs while communicating with the
* GData service.
*/
public void deleteEntry(String editUri, String authToken, String eTag)
throws AuthenticationException, ConflictDetectedException, PreconditionFailedException,
HttpException, ParseException, IOException, ForbiddenException,
ResourceNotFoundException {
try {
gDataClient.deleteEntry(editUri, authToken, eTag);
} catch (HttpException e) {
if (e.getStatusCode() == HttpException.SC_NOT_FOUND) {
// the server does not know about this entry.
// nothing to delete.
return;
}
convertHttpExceptionForWrites(null, "Unable to delete " + editUri, e);
}
}
private Entry parseEntry(Class entryClass, InputStream is) throws ParseException, IOException {
GDataParser parser = null;
try {
parser = gDataParserFactory.createParser(entryClass, is);
return parser.parseStandaloneEntry();
} finally {
if (parser != null) {
parser.close();
}
}
}
/**
* Submits a batch of operations.
*
* @param feedEntryClass the type of the entry to expect.
* @param batchUrl The url to which the batch is submitted.
* @param authToken The authentication token for this user.
* @param entries an enumeration of the entries to submit.
* @throws AuthenticationException Thrown if the server considers the
* authToken invalid.
* @throws HttpException if the service returns an error response
* @throws ParseException Thrown if the server response cannot be parsed.
* @throws IOException Thrown if an error occurs while communicating with the
* GData service.
*/
public GDataParser submitBatch(Class feedEntryClass, String batchUrl, String authToken,
Enumeration entries) throws AuthenticationException, HttpException, ParseException,
IOException, ForbiddenException {
GDataSerializer serializer = gDataParserFactory.createSerializer(entries);
try {
InputStream is =
gDataClient.submitBatch(batchUrl, authToken, getProtocolVersion(), serializer);
return gDataParserFactory.createParser(feedEntryClass, is);
} catch (HttpException e) {
convertHttpExceptionsForBatches("Could not submit batch " + batchUrl, e);
return null; // never reached.
}
}
protected void convertHttpExceptionForFeedReads(String message, HttpException cause)
throws AuthenticationException, ResourceGoneException, ResourceNotModifiedException,
HttpException, ForbiddenException {
switch (cause.getStatusCode()) {
case HttpException.SC_FORBIDDEN:
throw new ForbiddenException(message, cause);
case HttpException.SC_UNAUTHORIZED:
throw new AuthenticationException(message, cause);
case HttpException.SC_GONE:
throw new ResourceGoneException(message, cause);
case HttpException.SC_NOT_MODIFIED:
throw new ResourceNotModifiedException(message, cause);
default:
throw new HttpException(message + ": " + cause.getMessage(), cause.getStatusCode(), cause
.getResponseStream());
}
}
protected void convertHttpExceptionForEntryReads(String message, HttpException cause)
throws AuthenticationException, HttpException, ResourceNotFoundException,
ResourceNotModifiedException, ForbiddenException {
switch (cause.getStatusCode()) {
case HttpException.SC_FORBIDDEN:
throw new ForbiddenException(message, cause);
case HttpException.SC_UNAUTHORIZED:
throw new AuthenticationException(message, cause);
case HttpException.SC_NOT_FOUND:
throw new ResourceNotFoundException(message, cause);
case HttpException.SC_NOT_MODIFIED:
throw new ResourceNotModifiedException(message, cause);
default:
throw new HttpException(message + ": " + cause.getMessage(), cause.getStatusCode(), cause
.getResponseStream());
}
}
protected void convertHttpExceptionsForBatches(String message, HttpException cause)
throws AuthenticationException, ParseException, HttpException, ForbiddenException {
switch (cause.getStatusCode()) {
case HttpException.SC_FORBIDDEN:
throw new ForbiddenException(message, cause);
case HttpException.SC_UNAUTHORIZED:
throw new AuthenticationException(message, cause);
case HttpException.SC_BAD_REQUEST:
throw new ParseException(message + ": " + cause);
default:
throw new HttpException(message + ": " + cause.getMessage(), cause.getStatusCode(), cause
.getResponseStream());
}
}
protected void convertHttpExceptionForWrites(Class entryClass, String message,
HttpException cause)
throws ConflictDetectedException, AuthenticationException, PreconditionFailedException,
ParseException, HttpException, IOException, ForbiddenException,
ResourceNotFoundException {
switch (cause.getStatusCode()) {
case HttpException.SC_CONFLICT:
Entry entry = null;
if (entryClass != null) {
InputStream is = cause.getResponseStream();
if (is != null) {
entry = parseEntry(entryClass, cause.getResponseStream());
}
}
throw new ConflictDetectedException(entry);
case HttpException.SC_BAD_REQUEST:
throw new ParseException(message + ": " + cause);
case HttpException.SC_FORBIDDEN:
throw new ForbiddenException(message, cause);
case HttpException.SC_UNAUTHORIZED:
throw new AuthenticationException(message, cause);
case HttpException.SC_PRECONDITION_FAILED:
throw new PreconditionFailedException(message, cause);
case HttpException.SC_NOT_FOUND:
throw new ResourceNotFoundException(message, cause);
default:
throw new HttpException(message + ": " + cause.getMessage(), cause.getStatusCode(), cause
.getResponseStream());
}
}
}