blob: 3fcecc7dc49f2ab1e9bb831cd7cbf2ccfafa966e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010, 2018 The Eclipse Foundation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* The Eclipse Foundation - initial API and implementation
*******************************************************************************/
package org.eclipse.epp.internal.mpc.core.service;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpStatus;
import org.apache.http.client.fluent.Request;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.epp.internal.mpc.core.MarketplaceClientCore;
import org.eclipse.epp.internal.mpc.core.model.FavoriteList;
import org.eclipse.epp.internal.mpc.core.transport.httpclient.RequestTemplate;
import org.eclipse.epp.internal.mpc.core.util.URLUtil;
import org.eclipse.epp.mpc.core.model.IFavoriteList;
import org.eclipse.epp.mpc.core.model.INode;
import org.eclipse.epp.mpc.core.service.IMarketplaceService;
import org.eclipse.epp.mpc.core.service.IUserFavoritesService;
import org.eclipse.epp.mpc.core.service.QueryHelper;
import org.eclipse.epp.mpc.core.service.ServiceHelper;
import org.eclipse.osgi.util.NLS;
import org.eclipse.userstorage.IBlob;
import org.eclipse.userstorage.internal.Session;
import org.eclipse.userstorage.internal.util.IOUtil;
import org.eclipse.userstorage.internal.util.StringUtil;
import org.eclipse.userstorage.util.ConflictException;
import org.eclipse.userstorage.util.NoServiceException;
import org.eclipse.userstorage.util.NotFoundException;
import org.eclipse.userstorage.util.ProtocolException;
@SuppressWarnings("restriction")
public class UserFavoritesService extends AbstractDataStorageService implements IUserFavoritesService {
private static final String MARKETPLACE_USER_FAVORITES_ENDPOINT = "user/%s/favorites"; //$NON-NLS-1$
private static final String RANDOM_FAVORITE_LISTS_ENDPOINT = "marketplace/favorites/random"; //$NON-NLS-1$
private static final int MALFORMED_CONTENT_ERROR_CODE = 499;
private static final String TEMPLATE_VARIABLE = "%s"; //$NON-NLS-1$
/**
* Matches a single object/dict in a list, returning its body (i.e. without the braces) in its first match group.
* This only supports dicts with simple attributes. Nested dicts will result in wrong matches.
*/
private static final String JSON_LIST_OBJECTS_REGEX = "\\{([^\\{\\}]+)\\}"; //$NON-NLS-1$
private static final Pattern JSON_LIST_OBJECTS_PATTERN = Pattern.compile(JSON_LIST_OBJECTS_REGEX,
Pattern.MULTILINE);
/**
* Returns the body of the list value for the attribute with the given name, e.g. for <code>
* {"users":[{...},{...}], "count"="2"}
* </code> and the name "users" it will return "{...},{...}" in its first match group.
*/
private static final String JSON_ATTRIBUTE_OBJECT_LIST_REGEX = "\\{(?:.*,)?\\s*\"" + TEMPLATE_VARIABLE //$NON-NLS-1$
+ "\"\\s*:\\s*\\[((?:\\s*" + JSON_LIST_OBJECTS_REGEX + "\\s*,?\\s*)*)\\],.*\\}"; //$NON-NLS-1$ //$NON-NLS-2$
/**
* Matches a single string attribute in a dict. Returns the matched attribute name in the first match group and the
* attribute value in the second.
*/
private static final String JSON_ATTRIBUTE_REGEX = "(?<=[,\\{]|^)\\s*\"(" + TEMPLATE_VARIABLE //$NON-NLS-1$
+ ")\"\\s*:\\s*\"([^\"]*)\"\\s*(?=[,\\}]|$)"; //$NON-NLS-1$
private static final Pattern JSON_MPC_FAVORITES_PATTERN = Pattern
.compile(String.format(JSON_ATTRIBUTE_OBJECT_LIST_REGEX, "mpc_favorites"), Pattern.MULTILINE); //$NON-NLS-1$
private static final Pattern JSON_FAVORITE_LISTS_PATTERN = Pattern
.compile(String.format(JSON_ATTRIBUTE_OBJECT_LIST_REGEX, "users"), Pattern.MULTILINE); //$NON-NLS-1$
private static final Pattern JSON_USER_ID_ATTRIBUTE_PATTERN = Pattern
.compile(String.format(JSON_ATTRIBUTE_REGEX, "name"), Pattern.MULTILINE); //$NON-NLS-1$
private static final Pattern JSON_OWNER_ATTRIBUTE_PATTERN = Pattern
.compile(String.format(JSON_ATTRIBUTE_REGEX, "(?:full_)?name"), Pattern.MULTILINE); //$NON-NLS-1$
private static final Pattern JSON_NAME_ATTRIBUTE_PATTERN = Pattern
.compile(String.format(JSON_ATTRIBUTE_REGEX, "mpc_list_name"), Pattern.MULTILINE); //$NON-NLS-1$
private static final Pattern JSON_CONTENT_ID_ATTRIBUTE_PATTERN = Pattern
.compile(String.format(JSON_ATTRIBUTE_REGEX, "content_id"), Pattern.MULTILINE); //$NON-NLS-1$
private static final Pattern JSON_LIST_URL_ATTRIBUTE_PATTERN = Pattern
.compile(String.format(JSON_ATTRIBUTE_REGEX, "html_mpc_favorites_url"), Pattern.MULTILINE); //$NON-NLS-1$
private static final Pattern JSON_OWNER_ICON_ATTRIBUTE_PATTERN = Pattern
.compile(String.format(JSON_ATTRIBUTE_REGEX, "picture"), Pattern.MULTILINE); //$NON-NLS-1$
private static final Pattern JSON_OWNER_PROFILE_URL_ATTRIBUTE_PATTERN = Pattern
.compile(String.format(JSON_ATTRIBUTE_REGEX, "html_profile_url"), Pattern.MULTILINE); //$NON-NLS-1$
public static final Pattern FAVORITES_URL_PATTERN = Pattern
.compile("(?:^|/)user/([^/#?]+)(/favorites)?([/#?].*)?$"); //$NON-NLS-1$
private static final String KEY = "mpc_favorites"; //$NON-NLS-1$
private static final int RETRY_COUNT = 3;
private static final String SEPARATOR = ","; //$NON-NLS-1$
private final Map<String, Integer> favoritesCorrections = new HashMap<String, Integer>();
private final Set<String> favorites = new HashSet<String>();
protected IBlob getFavoritesBlob() {
return getStorageService().getBlob(KEY);
}
@Override
public Integer getFavoriteCount(INode node) {
Integer favorited = node.getFavorited();
if (favorited == null) {
return null;
}
Integer correction = favoritesCorrections.get(node.getId());
if (correction != null) {
favorited += correction;
}
return favorited;
}
public Set<String> getLastFavoriteIds() {
return Collections.unmodifiableSet(favorites);
}
@Override
public Set<String> getFavoriteIds(IProgressMonitor monitor)
throws NoServiceException, NotAuthorizedException, IllegalStateException, IOException {
SubMonitor progress = SubMonitor.convert(monitor, Messages.DefaultMarketplaceService_FavoritesRetrieve, 1000);
try {
String favoritesData = getFavoritesBlob().getContentsUTF();
progress.worked(950);//FIXME waiting for USS bug 488335 to have proper progress and cancelation
Set<String> result = parseFavoritesBlobData(favoritesData);
synchronized (this) {
favorites.clear();
favorites.addAll(result);
}
return result;
} catch (NotFoundException ex) {
//the user does not yet have favorites
return new LinkedHashSet<String>();
} catch (OperationCanceledException ex) {
throw processProtocolException(ex);
} catch (ProtocolException ex) {
throw processProtocolException(ex);
}
}
public List<INode> getLastFavorites() {
Set<String> favoriteIds = getLastFavoriteIds();
List<INode> favoriteNodes = toNodes(favoriteIds);
return favoriteNodes;
}
@Override
public List<INode> getFavorites(IProgressMonitor monitor)
throws NoServiceException, NotAuthorizedException, IllegalStateException, IOException {
Set<String> favoriteIds = getFavoriteIds(monitor);
List<INode> favoriteNodes = toNodes(favoriteIds);
return favoriteNodes;
}
@Override
public List<IFavoriteList> getRandomFavoriteLists(IProgressMonitor monitor) throws IOException {
URI serviceUri = getStorageService().getServiceUri();
final URI randomFavoritesUri = serviceUri.resolve(RANDOM_FAVORITE_LISTS_ENDPOINT);
return new AbstractJSONListRequest<IFavoriteList>(randomFavoritesUri, JSON_FAVORITE_LISTS_PATTERN) {
@Override
protected IFavoriteList parseListElement(String entryBody) {
String id = findFavoritesListId(entryBody);
if (id == null) {
return null;
}
String owner = findFavoritesListOwner(entryBody);
if (owner == null) {
owner = id;
}
String label = findFavoritesListLabel(entryBody);
if (label != null && (label.equals(id) || label.equals(owner))) {
label = null;
}
String favoritesListUrl = getFavoritesListUrl(entryBody, id);
if (favoritesListUrl == null) {
return null;
}
String icon = getAttribute(JSON_OWNER_ICON_ATTRIBUTE_PATTERN, null, entryBody);
String profileUrl = getAttribute(JSON_OWNER_PROFILE_URL_ATTRIBUTE_PATTERN, null, entryBody);
IFavoriteList favoritesByUserId = QueryHelper.favoritesByUserId(id);
((FavoriteList) favoritesByUserId).setOwner(owner);
((FavoriteList) favoritesByUserId).setOwnerProfileUrl(profileUrl);
((FavoriteList) favoritesByUserId).setName(label);
((FavoriteList) favoritesByUserId).setUrl(favoritesListUrl);
((FavoriteList) favoritesByUserId).setIcon(icon);
return favoritesByUserId;
}
}.execute(randomFavoritesUri);
}
private static String getAttribute(Pattern attributePattern, String attributeName, String entryBody) {
Matcher matcher = attributePattern.matcher(entryBody);
while (matcher.find()) {
String matchedName = matcher.group(1);
if (attributeName == null || attributeName.equals(matchedName)) {
return matcher.group(2);
}
}
return null;
}
private String getFavoritesListUrl(String entryBody, String id) {
String marketplaceBaseUri = getMarketplaceBaseUri();
//We use the HTML URL shown in the web frontend instead of the API URL, because that's what's advertised
String explicitUrl = getAttribute(JSON_LIST_URL_ATTRIBUTE_PATTERN, null, entryBody);
if (explicitUrl != null && explicitUrl.trim().length() > 0) {
try {
//Check that it's a valid URL
URL url = URLUtil.toURL(explicitUrl);
URI uri = url.toURI();
if (!uri.isAbsolute()) {
uri = new URI(marketplaceBaseUri).resolve(uri);
}
return uri.toURL().toString();
} catch (Exception ex) {
MarketplaceClientCore
.error(NLS.bind("Invalid list URL {0} for favorites list {1} - falling back to default URL",
explicitUrl, id),
ex);
}
}
String path = String.format(MARKETPLACE_USER_FAVORITES_ENDPOINT, URLUtil.encode(id));
return URLUtil.appendPath(marketplaceBaseUri, path);
}
private String getMarketplaceBaseUri() {
String marketplaceBaseUri = getStorageService().getMarketplaceBaseUri();
if (marketplaceBaseUri != null) {
return marketplaceBaseUri;
}
IMarketplaceService defaultMarketplaceService = ServiceHelper.getMarketplaceServiceLocator()
.getDefaultMarketplaceService();
if (defaultMarketplaceService != null) {
return defaultMarketplaceService.getBaseUrl().toString();
}
return DefaultMarketplaceService.DEFAULT_SERVICE_LOCATION;
}
private static String findFavoritesListOwner(String entryBody) {
return findFavoritesNameOrId(entryBody, JSON_OWNER_ATTRIBUTE_PATTERN);
}
private static String findFavoritesListLabel(String entryBody) {
return findFavoritesNameOrId(entryBody, JSON_NAME_ATTRIBUTE_PATTERN);
}
private static String findFavoritesListId(String entryBody) {
return findFavoritesNameOrId(entryBody, JSON_USER_ID_ATTRIBUTE_PATTERN);
}
private static String findFavoritesNameOrId(String entryBody, Pattern pattern) {
String result = null;
Matcher matcher = pattern.matcher(entryBody);
while (matcher.find()) {
String name = matcher.group(1);
String value = matcher.group(2);
if ("name".equals(name)) { //$NON-NLS-1$
//remember, but try to find a better match
if (result == null) {
result = value;
}
} else {
return value;
}
}
return result;
}
private static List<INode> toNodes(Collection<String> favoriteIds) {
List<INode> favoriteNodes = new ArrayList<INode>(favoriteIds.size());
for (String nodeId : favoriteIds) {
INode node = QueryHelper.nodeById(nodeId);
favoriteNodes.add(node);
}
return favoriteNodes;
}
protected Set<String> parseFavoritesBlobData(String favoritesData) {
Set<String> favoriteIds = new LinkedHashSet<String>();
for (StringTokenizer tokenizer = new StringTokenizer(favoritesData, SEPARATOR); tokenizer.hasMoreTokens();) {
String nodeId = tokenizer.nextToken();
favoriteIds.add(nodeId);
}
return favoriteIds;
}
@Override
public void setFavorites(Collection<? extends INode> nodes, IProgressMonitor monitor)
throws NoServiceException, ConflictException, NotAuthorizedException, IllegalStateException, IOException {
SubMonitor progress = SubMonitor.convert(monitor, Messages.UserFavoritesService_SettingUserFavorites, 1000);
String favoritesData = createFavoritesBlobData(nodes);
try {
if (favoritesData == null || "".equals(favoritesData)) { //$NON-NLS-1$
getFavoritesBlob().delete();
} else {
getFavoritesBlob().setContentsUTF(favoritesData);
}
progress.worked(900);//FIXME waiting for USS bug 488335 to have proper progress and cancelation
} catch (OperationCanceledException ex) {
throw processProtocolException(ex);
} catch (ProtocolException ex) {
throw processProtocolException(ex);
}
synchronized (this) {
SubMonitor notifyNewProgress = SubMonitor.convert(progress.newChild(50), nodes.size());
Set<String> newFavorites = new HashSet<String>();
for (INode node : nodes) {
String id = node.getId();
if (newFavorites.add(id)) {
boolean newFavorite = favorites.add(id);
if (newFavorite) {
didChangeFavorite(id, true);
}
}
notifyNewProgress.worked(1);
}
SubMonitor notifyRemovedProgress = SubMonitor.convert(progress.newChild(50), favorites.size());
for (Iterator<String> i = favorites.iterator(); i.hasNext();) {
String id = i.next();
if (!newFavorites.contains(id)) {
i.remove();
didChangeFavorite(id, false);
}
notifyRemovedProgress.worked(1);
}
}
}
private void didChangeFavorite(String nodeId, boolean favorite) {
Integer correction = favoritesCorrections.get(nodeId);
if (correction == null) {
correction = 0;
}
correction += favorite ? 1 : -1;
if (correction < -1) {
correction = -1;
} else if (correction > 1) {
correction = 1;
}
if (correction == 0) {
favoritesCorrections.remove(nodeId);
} else {
favoritesCorrections.put(nodeId, correction);
}
}
protected String createFavoritesBlobData(Collection<? extends INode> nodes) {
if (nodes.isEmpty()) {
return null;
}
List<String> nodeIds = new ArrayList<String>(nodes.size());
for (INode node : nodes) {
nodeIds.add(node.getId());
}
Collections.sort(nodeIds);
StringBuilder builder = new StringBuilder();
boolean first = true;
for (String nodeId : nodeIds) {
if (first) {
first = false;
} else {
builder.append(SEPARATOR);
}
builder.append(nodeId);
}
return builder.toString();
}
@Override
public void setFavorite(INode node, boolean favorite, IProgressMonitor monitor)
throws NotAuthorizedException, ConflictException, IOException {
alterFavorites(Collections.singleton(node), favorite, monitor);
}
@Override
public void addFavorites(Collection<? extends INode> nodes, IProgressMonitor monitor)
throws NotAuthorizedException, ConflictException, IOException {
alterFavorites(nodes, true, monitor);
}
@Override
public void removeFavorites(Collection<? extends INode> nodes, IProgressMonitor monitor)
throws NotAuthorizedException, ConflictException, IOException {
alterFavorites(nodes, false, monitor);
}
private void alterFavorites(Collection<? extends INode> nodes, boolean favorite, IProgressMonitor monitor)
throws NotAuthorizedException, ConflictException, IOException {
SubMonitor progress = SubMonitor.convert(monitor, Messages.UserFavoritesService_SettingUserFavorites, 1000);
ConflictException conflictException = null;
for (int i = 0; i < RETRY_COUNT; i++) {
try {
progress.setWorkRemaining(1000);
doAlterFavorites(nodes, favorite, progress.newChild(800));
progress.done();
return;
} catch (ConflictException e) {
conflictException = e;
} catch (OperationCanceledException ex) {
throw processProtocolException(ex);
} catch (ProtocolException ex) {
throw processProtocolException(ex);
}
}
if (conflictException != null) {
throw conflictException;
}
}
private void doAlterFavorites(Collection<? extends INode> nodes, boolean favorite, IProgressMonitor monitor)
throws ConflictException, IOException {
SubMonitor progress = SubMonitor.convert(monitor, Messages.UserFavoritesService_SettingUserFavorites, 1000);
List<INode> favorites = getFavorites(progress.newChild(300));
for (INode node : nodes) {
INode currentFavorite = QueryHelper.findById(favorites, node);
if (currentFavorite != null && !favorite) {
favorites.remove(currentFavorite);
} else if (currentFavorite == null && favorite) {
favorites.add(node);
}
}
setFavorites(favorites, progress.newChild(700));
}
@Override
public List<INode> getFavorites(URI uri, IProgressMonitor monitor) throws IOException {
List<String> nodeIds = getFavoriteIds(uri, monitor);
return toNodes(nodeIds);
}
private URI normalizeURI(URI uri) {
validateUri(uri);
String marketplaceBaseUri = getMarketplaceBaseUri();
marketplaceBaseUri = URLUtil.appendPath(marketplaceBaseUri, ""); //$NON-NLS-1$
marketplaceBaseUri = URLUtil.setScheme(marketplaceBaseUri, uri.getScheme());
if (!uri.toString().startsWith(marketplaceBaseUri)) {
return uri;
}
Matcher matcher = FAVORITES_URL_PATTERN.matcher(uri.toString());
if (matcher.find()) {
String name = matcher.group(1);
return getStorageService().getServiceUri()
.resolve("marketplace/favorites/?name=" + URLUtil.urlEncode(name));
}
return uri;
}
public static void validateUri(URI uri) {
if ("".equals(uri.toString()) //$NON-NLS-1$
|| ((uri.getHost() == null || "".equals(uri.getHost())) //$NON-NLS-1$
&& (uri.getScheme() != null && uri.getScheme().toLowerCase().startsWith("http"))) //$NON-NLS-1$
|| (uri.getScheme() == null && (uri.getPath() == null || "".equals(uri.getPath())))) { //$NON-NLS-1$
//incomplete uri
throw new IllegalArgumentException(
new URISyntaxException(uri.toString(), Messages.UserFavoritesService_uriMissingHost));
}
}
@Override
public List<String> getFavoriteIds(final URI uri, IProgressMonitor monitor) throws IOException {
URI normalizedUri = normalizeURI(uri);
try {
return new AbstractJSONListRequest<String>(normalizedUri, JSON_MPC_FAVORITES_PATTERN) {
@Override
protected String parseListElement(String listElement) {
Matcher contentIdMatcher = JSON_CONTENT_ID_ATTRIBUTE_PATTERN.matcher(listElement);
if (contentIdMatcher.find()) {
return contentIdMatcher.group(2);
}
return null;
}
}.execute(uri);
} catch (FileNotFoundException e) {
return new ArrayList<String>();
}
}
private static ProtocolException malformedContentException(final URI endpoint, String body) {
return new ProtocolException("GET", endpoint, "1.1", MALFORMED_CONTENT_ERROR_CODE, //$NON-NLS-1$ //$NON-NLS-2$
"Malformed response content: " + body); //$NON-NLS-1$
}
public static boolean isInvalidFavoritesListException(Throwable error) {
while (error != null) {
if (isMalformedContentException(error) || isNotFoundException(error)) {
return true;
}
if (error instanceof CoreException) {
CoreException coreException = (CoreException) error;
IStatus status = coreException.getStatus();
if (status.isMultiStatus()) {
for (IStatus childStatus : status.getChildren()) {
if (childStatus.getException() != null
&& isInvalidFavoritesListException(childStatus.getException())) {
return true;
}
}
}
}
error = error.getCause();
}
return false;
}
public static boolean isInvalidUrlException(Throwable error) {
while (error != null) {
if (error instanceof URISyntaxException || error instanceof MalformedURLException) {
return true;
}
if (error instanceof CoreException) {
CoreException coreException = (CoreException) error;
IStatus status = coreException.getStatus();
if (status.isMultiStatus()) {
for (IStatus childStatus : status.getChildren()) {
if (childStatus.getException() != null && isInvalidUrlException(childStatus.getException())) {
return true;
}
}
}
}
error = error.getCause();
}
return false;
}
private static boolean isMalformedContentException(Throwable error) {
if (error instanceof ProtocolException) {
ProtocolException protocolException = (ProtocolException) error;
switch (protocolException.getStatusCode()) {
case HttpStatus.SC_BAD_REQUEST:
case HttpStatus.SC_METHOD_NOT_ALLOWED:
case HttpStatus.SC_NOT_ACCEPTABLE:
case HttpStatus.SC_EXPECTATION_FAILED:
case MALFORMED_CONTENT_ERROR_CODE:
return true;
default:
return false;
}
}
return false;
}
private static boolean isNotFoundException(Throwable error) {
if (error instanceof NotFoundException) {
return true;
}
if (error instanceof ProtocolException) {
ProtocolException protocolException = (ProtocolException) error;
switch (protocolException.getStatusCode()) {
case HttpStatus.SC_NOT_FOUND:
case HttpStatus.SC_GONE:
return true;
default:
return false;
}
}
return false;
}
private static abstract class AbstractJSONListRequest<T> extends RequestTemplate<List<T>> {
private final URI uri;
private final Pattern listAttributePattern;
private AbstractJSONListRequest(URI uri, Pattern listAttributePattern) {
this.uri = uri;
this.listAttributePattern = listAttributePattern;
}
@Override
protected Request configureRequest(Request request, URI uri) {
return super.configureRequest(request, uri).setHeader(HttpHeaders.USER_AGENT, Session.USER_AGENT_ID)
.addHeader(HttpHeaders.CONTENT_TYPE, Session.APPLICATION_JSON) //
.addHeader(HttpHeaders.ACCEPT, Session.APPLICATION_JSON);
}
@Override
protected List<T> handleResponseStream(InputStream content, Charset charset) throws IOException {
String body = read(content, charset);
body = body.trim();
return handleBody(uri, body);
}
private static String read(InputStream in, Charset charset) throws IOException {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IOUtil.copy(in, baos);
byte[] bytes = baos.toByteArray();
if (bytes == null) {
return StringUtil.EMPTY;
}
return new String(bytes, charset == null ? StringUtil.UTF8 : charset.name());
} catch (RuntimeException ex) {
Throwable cause = ex.getCause();
if (cause instanceof IOException) {
throw (IOException) cause;
}
throw ex;
} finally {
IOUtil.close(in);
}
}
protected List<T> handleBody(final URI uri, String body) throws ProtocolException {
List<T> favoriteIds = new ArrayList<T>();
if (!"".equals(body)) { //$NON-NLS-1$
Matcher matcher = listAttributePattern.matcher(body);
if (matcher.find()) {
String listBody = matcher.group(1);
Matcher entryMatcher = JSON_LIST_OBJECTS_PATTERN.matcher(listBody);
while (entryMatcher.find()) {
String listElement = entryMatcher.group(1);
T parsedElement = parseListElement(listElement);
if (parsedElement != null) {
favoriteIds.add(parsedElement);
}
}
} else {
throw malformedContentException(uri, body);
}
}
return favoriteIds;
}
protected abstract T parseListElement(String listElement);
@Override
protected Request createRequest(URI uri) {
return Request.Get(uri);
}
}
}