blob: cc2ff55c6bea66ecf8cdd70b33d17f94788cc245 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010 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
* Yatta Solutions - bug 314936, bug 398200, public API (bug 432803)
*******************************************************************************/
package org.eclipse.epp.internal.mpc.ui.catalog;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.epp.internal.mpc.core.ServiceLocator;
import org.eclipse.epp.internal.mpc.core.service.Identifiable;
import org.eclipse.epp.internal.mpc.core.service.Node;
import org.eclipse.epp.internal.mpc.core.service.SearchResult;
import org.eclipse.epp.internal.mpc.ui.MarketplaceClientUi;
import org.eclipse.epp.internal.mpc.ui.catalog.MarketplaceCategory.Contents;
import org.eclipse.epp.internal.mpc.ui.util.ConcurrentTaskManager;
import org.eclipse.epp.mpc.core.model.ICategories;
import org.eclipse.epp.mpc.core.model.ICategory;
import org.eclipse.epp.mpc.core.model.IIdentifiable;
import org.eclipse.epp.mpc.core.model.IIus;
import org.eclipse.epp.mpc.core.model.IMarket;
import org.eclipse.epp.mpc.core.model.INews;
import org.eclipse.epp.mpc.core.model.INode;
import org.eclipse.epp.mpc.core.model.ISearchResult;
import org.eclipse.epp.mpc.core.service.IMarketplaceService;
import org.eclipse.epp.mpc.core.service.IMarketplaceServiceLocator;
import org.eclipse.epp.mpc.ui.CatalogDescriptor;
import org.eclipse.epp.mpc.ui.MarketplaceUrlHandler;
import org.eclipse.equinox.internal.p2.discovery.AbstractDiscoveryStrategy;
import org.eclipse.equinox.internal.p2.discovery.model.CatalogCategory;
import org.eclipse.equinox.internal.p2.discovery.model.CatalogItem;
import org.eclipse.equinox.internal.p2.discovery.model.Icon;
import org.eclipse.equinox.internal.p2.discovery.model.Overview;
import org.eclipse.equinox.internal.p2.discovery.model.Tag;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.osgi.util.NLS;
/**
* @author David Green
*/
public class MarketplaceDiscoveryStrategy extends AbstractDiscoveryStrategy {
private static final String DOT_FEATURE_DOT_GROUP = ".feature.group"; //$NON-NLS-1$
private static final Pattern BREAK_PATTERN = Pattern.compile("<!--\\s*break\\s*-->"); //$NON-NLS-1$
protected final CatalogDescriptor catalogDescriptor;
private final IMarketplaceService marketplaceService;
private MarketplaceCatalogSource source;
private MarketplaceInfo marketplaceInfo;
private Map<String, IInstallableUnit> featureIUById;
public MarketplaceDiscoveryStrategy(CatalogDescriptor catalogDescriptor) {
if (catalogDescriptor == null) {
throw new IllegalArgumentException();
}
this.catalogDescriptor = catalogDescriptor;
marketplaceService = createMarketplaceService();//use deprecated method in case someone has overridden it
source = new MarketplaceCatalogSource(marketplaceService);
marketplaceInfo = MarketplaceInfo.getInstance();
}
/**
* @deprecated get a marketplace service from the registered {@link IMarketplaceServiceLocator} OSGi service instead
*/
@Deprecated
public IMarketplaceService createMarketplaceService() {
return acquireMarketplaceService();
}
protected IMarketplaceService acquireMarketplaceService() {
String baseUrl = this.catalogDescriptor.getUrl().toExternalForm();
return ServiceLocator.getCompatibilityLocator().getMarketplaceService(baseUrl);
}
/**
* @deprecated moved to {@link ServiceLocator#computeDefaultRequestMetaParameters()}
*/
@Deprecated
public static Map<String, String> computeDefaultRequestMetaParameters() {
return ServiceLocator.computeDefaultRequestMetaParameters();
}
@Override
public void dispose() {
if (source != null) {
source.dispose();
source = null;
}
if (marketplaceInfo != null) {
marketplaceInfo.save();
marketplaceInfo = null;
}
super.dispose();
}
@Override
public void performDiscovery(IProgressMonitor monitor) throws CoreException {
if (monitor == null) {
monitor = new NullProgressMonitor();
}
final int totalWork = 10000000;
final int workSegment = totalWork / 3;
monitor.beginTask(Messages.MarketplaceDiscoveryStrategy_loadingMarketplace, totalWork);
try {
MarketplaceCategory catalogCategory = findMarketplaceCategory(new SubProgressMonitor(monitor, workSegment));
catalogCategory.setContents(Contents.FEATURED);
ISearchResult featured = marketplaceService.featured(new SubProgressMonitor(monitor, workSegment));
handleSearchResult(catalogCategory, featured, new SubProgressMonitor(monitor, workSegment));
maybeAddCatalogItem(catalogCategory);
} finally {
monitor.done();
}
}
protected void handleSearchResult(MarketplaceCategory catalogCategory, ISearchResult result,
final IProgressMonitor monitor) {
if (!result.getNodes().isEmpty()) {
int totalWork = 10000000;
monitor.beginTask(Messages.MarketplaceDiscoveryStrategy_loadingResources, totalWork);
ConcurrentTaskManager executor = new ConcurrentTaskManager(result.getNodes().size(),
Messages.MarketplaceDiscoveryStrategy_loadingResources);
try {
for (final INode node : result.getNodes()) {
final MarketplaceNodeCatalogItem catalogItem = new MarketplaceNodeCatalogItem();
catalogItem.setMarketplaceUrl(catalogDescriptor.getUrl());
catalogItem.setId(node.getId());
catalogItem.setName(getCatalogItemName(node));
catalogItem.setCategoryId(catalogCategory.getId());
ICategories categories = node.getCategories();
if (categories != null) {
for (ICategory category : categories.getCategory()) {
catalogItem.addTag(new Tag(ICategory.class, category.getId(), category.getName()));
}
}
catalogItem.setData(node);
catalogItem.setSource(source);
catalogItem.setLicense(node.getLicense());
IIus ius = node.getIus();
if (ius != null) {
List<String> discoveryIus = new ArrayList<String>(ius.getIu());
for (int x = 0; x < discoveryIus.size(); ++x) {
String iu = discoveryIus.get(x);
if (!iu.endsWith(DOT_FEATURE_DOT_GROUP)) {
discoveryIus.set(x, iu + DOT_FEATURE_DOT_GROUP);
}
}
catalogItem.setInstallableUnits(discoveryIus);
}
if (node.getShortdescription() == null && node.getBody() != null) {
// bug 306653 <!--break--> marks the end of the short description.
String descriptionText = node.getBody();
Matcher matcher = BREAK_PATTERN.matcher(node.getBody());
if (matcher.find()) {
int start = matcher.start();
if (start > 0) {
String shortDescriptionText = descriptionText.substring(0, start).trim();
if (shortDescriptionText.length() > 0) {
descriptionText = shortDescriptionText;
}
}
}
catalogItem.setDescription(descriptionText);
} else {
catalogItem.setDescription(node.getShortdescription());
}
catalogItem.setProvider(node.getCompanyname());
String updateurl = node.getUpdateurl();
if (updateurl != null) {
try {
// trim is important!
updateurl = updateurl.trim();
new URL(updateurl);
catalogItem.setSiteUrl(updateurl);
} catch (MalformedURLException e) {
// don't use malformed URLs
}
}
if (node.getBody() != null || node.getScreenshot() != null) {
final Overview overview = new Overview();
overview.setItem(catalogItem);
overview.setSummary(node.getBody());
overview.setUrl(node.getUrl());
catalogItem.setOverview(overview);
if (node.getScreenshot() != null) {
if (!source.getResourceProvider().containsResource(node.getScreenshot())) {
executor.submit(new AbstractResourceRunnable(monitor, source.getResourceProvider(),
node.getScreenshot()) {
@Override
protected void resourceRetrieved() {
overview.setScreenshot(node.getScreenshot());
}
});
} else {
overview.setScreenshot(node.getScreenshot());
}
}
}
if (node.getImage() != null) {
// FIXME: icon sizing
if (!source.getResourceProvider().containsResource(node.getImage())) {
executor.submit(new AbstractResourceRunnable(monitor, source.getResourceProvider(),
node.getImage()) {
@Override
protected void resourceRetrieved() {
createIcon(catalogItem, node);
}
});
} else {
createIcon(catalogItem, node);
}
}
items.add(catalogItem);
marketplaceInfo.map(catalogItem.getMarketplaceUrl(), node);
catalogItem.setInstalled(marketplaceInfo.computeInstalled(computeInstalledFeatures(monitor), node));
}
try {
executor.waitUntilFinished(new SubProgressMonitor(monitor, totalWork - 10));
} catch (CoreException e) {
// just log, since this is expected to occur frequently
MarketplaceClientUi.error(e);
}
} finally {
executor.shutdownNow();
monitor.done();
}
if (result.getMatchCount() != null) {
catalogCategory.setMatchCount(result.getMatchCount());
if (result.getMatchCount() > result.getNodes().size()) {
// add an item here to indicate that the search matched more items than were returned by the server
addCatalogItem(catalogCategory);
}
}
}
}
private String getCatalogItemName(INode node) {
String name = node.getName();
String version = node.getVersion();
return version == null || version.length() == 0 ? name : NLS.bind(
Messages.MarketplaceDiscoveryStrategy_Name_and_Version, name, version);
}
public void maybeAddCatalogItem(MarketplaceCategory catalogCategory) {
if (!items.isEmpty()) {
CatalogItem catalogItem = items.get(items.size() - 1);
if (catalogItem.getData() != catalogDescriptor) {
addCatalogItem(catalogCategory);
}
}
}
public void addCatalogItem(MarketplaceCategory catalogCategory) {
CatalogItem catalogItem = new CatalogItem();
catalogItem.setSource(source);
catalogItem.setData(catalogDescriptor);
catalogItem.setId(catalogDescriptor.getUrl().toString());
catalogItem.setCategoryId(catalogCategory.getId());
items.add(catalogItem);
}
private void createIcon(CatalogItem catalogItem, final INode node) {
Icon icon = new Icon();
// don't know the size
icon.setImage32(node.getImage());
icon.setImage48(node.getImage());
icon.setImage64(node.getImage());
catalogItem.setIcon(icon);
}
public void performQuery(IMarket market, ICategory category, String queryText, IProgressMonitor monitor)
throws CoreException {
final int totalWork = 1000000;
SubMonitor progress = SubMonitor.convert(monitor, Messages.MarketplaceDiscoveryStrategy_searchingMarketplace,
totalWork);
try {
ISearchResult result;
MarketplaceCategory catalogCategory = findMarketplaceCategory(progress.newChild(1));
catalogCategory.setContents(Contents.QUERY);
try {
//check if the query matches a node url and just retrieve that node
result = performNodeQuery(queryText, progress.newChild(totalWork / 2));
} catch (CoreException ex) {
// node not found, continue with regular query
result = null;
}
if (result == null) {
//regular query
//resolve market and category if necessary
IMarket resolvedMarket;
ICategory resolvedCategory;
try {
resolvedMarket = resolve(market, catalogCategory.getMarkets());
resolvedCategory = resolveCategory(category, catalogCategory.getMarkets());
} catch (IllegalArgumentException ex) {
throw new CoreException(MarketplaceClientUi.computeStatus(ex, Messages.MarketplaceDiscoveryStrategy_invalidFilter));
} catch (NoSuchElementException ex) {
throw new CoreException(MarketplaceClientUi.computeStatus(ex, Messages.MarketplaceDiscoveryStrategy_unknownFilter));
}
progress.setWorkRemaining(totalWork);
result = marketplaceService.search(resolvedMarket, resolvedCategory, queryText,
progress.newChild(totalWork / 2));
}
handleSearchResult(catalogCategory, result, progress.newChild(totalWork / 2));
if (result.getNodes().isEmpty()) {
catalogCategory.setMatchCount(0);
addCatalogItem(catalogCategory);
}
} finally {
progress.done();
}
}
private ICategory resolveCategory(ICategory category, List<? extends IMarket> markets)
throws IllegalArgumentException, NoSuchElementException {
if (category != null && category.getId() == null) {
//need to resolve
if (category.getUrl() == null && category.getName() == null) {
throw new IllegalArgumentException(NLS.bind(Messages.MarketplaceDiscoveryStrategy_unidentifiableItem,
category));
}
for (IMarket market : markets) {
List<? extends ICategory> categories = market.getCategory();
ICategory resolved = resolve(category, categories);
if (resolved != null) {
return resolved;
}
}
if (category.getUrl() != null) {
throw new NoSuchElementException(NLS.bind(Messages.MarketplaceDiscoveryStrategy_noUrlMatch,
category.getUrl()));
} else {
throw new NoSuchElementException(NLS.bind(Messages.MarketplaceDiscoveryStrategy_noNameMatch,
category.getName()));
}
}
return category;
}
private <T extends IIdentifiable> T resolve(T id, List<? extends T> candidates) throws IllegalArgumentException,
NoSuchElementException {
if (id != null && id.getId() == null) {
//need to resolve
if (id.getUrl() == null && id.getName() == null) {
throw new IllegalArgumentException(NLS.bind(
Messages.MarketplaceDiscoveryStrategy_unidentifiableItem, id));
}
for (T candidate : candidates) {
if (Identifiable.matches(candidate, id)) {
return candidate;
}
}
if (id.getUrl() != null) {
throw new NoSuchElementException(NLS.bind(Messages.MarketplaceDiscoveryStrategy_noUrlMatch, id.getUrl()));
} else {
throw new NoSuchElementException(NLS.bind(Messages.MarketplaceDiscoveryStrategy_noNameMatch, id.getName()));
}
}
return id;
}
private ISearchResult performNodeQuery(String nodeUrl, IProgressMonitor progress) throws CoreException {
final INode[] queryNode = new INode[1];
MarketplaceUrlHandler urlHandler = new MarketplaceUrlHandler() {
@Override
protected boolean handleNode(CatalogDescriptor descriptor, String url, INode node) {
queryNode[0] = node;
return true;
}
};
if (urlHandler.handleUri(nodeUrl) && queryNode[0] != null) {
INode node = marketplaceService.getNode(queryNode[0], progress);
SearchResult result = new SearchResult();
result.setMatchCount(1);
result.setNodes(Collections.singletonList((Node) node));
return result;
}
return null;
}
public void recent(IProgressMonitor monitor) throws CoreException {
final int totalWork = 1000000;
monitor.beginTask(Messages.MarketplaceDiscoveryStrategy_searchingMarketplace, totalWork);
try {
MarketplaceCategory catalogCategory = findMarketplaceCategory(new SubProgressMonitor(monitor, 1));
catalogCategory.setContents(Contents.RECENT);
ISearchResult result = marketplaceService.recent(new SubProgressMonitor(monitor, totalWork / 2));
handleSearchResult(catalogCategory, result, new SubProgressMonitor(monitor, totalWork / 2));
maybeAddCatalogItem(catalogCategory);
} finally {
monitor.done();
}
}
public void featured(IProgressMonitor monitor, final IMarket market, final ICategory category) throws CoreException {
final int totalWork = 1000000;
monitor.beginTask(Messages.MarketplaceDiscoveryStrategy_searchingMarketplace, totalWork);
try {
MarketplaceCategory catalogCategory = findMarketplaceCategory(new SubProgressMonitor(monitor, 1));
catalogCategory.setContents(Contents.FEATURED);
ISearchResult result = marketplaceService.featured(market, category,
new SubProgressMonitor(monitor, totalWork / 2));
handleSearchResult(catalogCategory, result, new SubProgressMonitor(monitor, totalWork / 2));
maybeAddCatalogItem(catalogCategory);
} finally {
monitor.done();
}
}
public void popular(IProgressMonitor monitor) throws CoreException {
final int totalWork = 1000000;
monitor.beginTask(Messages.MarketplaceDiscoveryStrategy_searchingMarketplace, totalWork);
try {
MarketplaceCategory catalogCategory = findMarketplaceCategory(new SubProgressMonitor(monitor, 1));
catalogCategory.setContents(Contents.POPULAR);
ISearchResult result = marketplaceService.popular(new SubProgressMonitor(monitor, totalWork / 2));
handleSearchResult(catalogCategory, result, new SubProgressMonitor(monitor, totalWork / 2));
maybeAddCatalogItem(catalogCategory);
} finally {
monitor.done();
}
}
public void installed(IProgressMonitor monitor) throws CoreException {
final int totalWork = 1000000;
monitor.beginTask(Messages.MarketplaceDiscoveryStrategy_findingInstalled, totalWork);
try {
MarketplaceCategory catalogCategory = findMarketplaceCategory(new SubProgressMonitor(monitor, 1));
catalogCategory.setContents(Contents.INSTALLED);
SearchResult result = new SearchResult();
result.setNodes(new ArrayList<Node>());
Set<String> installedFeatures = computeInstalledFeatures(monitor);
if (!monitor.isCanceled()) {
Set<INode> catalogNodes = marketplaceInfo.computeInstalledNodes(catalogDescriptor.getUrl(),
installedFeatures);
if (!catalogNodes.isEmpty()) {
int unitWork = totalWork / (2 * catalogNodes.size());
for (INode node : catalogNodes) {
node = marketplaceService.getNode(node, monitor);
result.getNodes().add((Node) node);
monitor.worked(unitWork);
}
} else {
monitor.worked(totalWork / 2);
}
handleSearchResult(catalogCategory, result, new SubProgressMonitor(monitor, totalWork / 2));
}
} finally {
monitor.done();
}
}
public void performQuery(IProgressMonitor monitor, Set<String> nodeIds) throws CoreException {
Set<INode> nodes = new HashSet<INode>();
for (String nodeId : nodeIds) {
Node node = new Node();
node.setId(nodeId);
nodes.add(node);
}
performNodeQuery(monitor, nodes);
}
public void performNodeQuery(IProgressMonitor monitor, Set<? extends INode> nodes) throws CoreException {
final int totalWork = 1000000;
monitor.beginTask(Messages.MarketplaceDiscoveryStrategy_searchingMarketplace, totalWork);
try {
MarketplaceCategory catalogCategory = findMarketplaceCategory(new SubProgressMonitor(monitor, 1));
catalogCategory.setContents(Contents.QUERY);
SearchResult result = new SearchResult();
result.setNodes(new ArrayList<Node>());
if (!monitor.isCanceled()) {
if (!nodes.isEmpty()) {
int unitWork = totalWork / (2 * nodes.size());
for (INode node : nodes) {
node = marketplaceService.getNode(node, monitor);
result.getNodes().add((Node) node);
monitor.worked(unitWork);
}
} else {
monitor.worked(totalWork / 2);
}
result.setMatchCount(result.getNodes().size());
handleSearchResult(catalogCategory, result, new SubProgressMonitor(monitor, totalWork / 2));
maybeAddCatalogItem(catalogCategory);
}
} finally {
monitor.done();
}
}
protected Set<String> computeInstalledFeatures(IProgressMonitor monitor) {
return computeInstalledIUs(monitor).keySet();
}
protected synchronized Map<String, IInstallableUnit> computeInstalledIUs(IProgressMonitor monitor) {
if (featureIUById == null) {
featureIUById = MarketplaceClientUi.computeInstalledIUsById(monitor);
}
return featureIUById;
}
protected MarketplaceCategory findMarketplaceCategory(IProgressMonitor monitor) throws CoreException {
MarketplaceCategory catalogCategory = null;
monitor.beginTask(Messages.MarketplaceDiscoveryStrategy_catalogCategory, 10000);
try {
for (CatalogCategory candidate : getCategories()) {
if (candidate.getSource() == source) {
catalogCategory = (MarketplaceCategory) candidate;
}
}
if (catalogCategory == null) {
List<? extends IMarket> markets = marketplaceService.listMarkets(new SubProgressMonitor(monitor, 10000));
// marketplace has markets and categories, however a node and/or category can appear in multiple
// markets. This doesn't match well with discovery's concept of a category. Discovery requires all
// items to be in a category, so we use a single root category and tagging.
catalogCategory = new MarketplaceCategory();
catalogCategory.setId("<root>"); //$NON-NLS-1$
catalogCategory.setName("<root>"); //$NON-NLS-1$
catalogCategory.setSource(source);
catalogCategory.setMarkets(markets);
categories.add(catalogCategory);
}
} finally {
monitor.done();
}
return catalogCategory;
}
public INews performNewsDiscovery(IProgressMonitor monitor) throws CoreException {
return marketplaceService.news(monitor);
}
public void installErrorReport(IProgressMonitor monitor, IStatus result, Set<CatalogItem> items,
IInstallableUnit[] operationIUs, String resolutionDetails) throws CoreException {
monitor.beginTask(Messages.MarketplaceDiscoveryStrategy_sendingErrorNotification, 100);
try {
Set<Node> nodes = new HashSet<Node>();
for (CatalogItem item : items) {
Object data = item.getData();
if (data instanceof INode) {
nodes.add((Node) data);
}
}
Set<String> iuIdsAndVersions = new HashSet<String>();
for (IInstallableUnit iu : operationIUs) {
String id = iu.getId();
String version = iu.getVersion() == null ? null : iu.getVersion().toString();
iuIdsAndVersions.add(id + "," + version); //$NON-NLS-1$
}
marketplaceService.reportInstallError(result, nodes, iuIdsAndVersions, resolutionDetails, monitor);
} finally {
monitor.done();
}
}
}