blob: 5a95223a96df68456aa067c08e6fe230e6e6fa4f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011 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:
* Tasktop Technologies - initial API and implementation
* Yatta Solutions - news (bug 401721), public API (bug 432803)
*******************************************************************************/
package org.eclipse.epp.mpc.ui;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.epp.internal.mpc.core.MarketplaceClientCore;
import org.eclipse.epp.internal.mpc.core.model.Node;
import org.eclipse.epp.internal.mpc.core.service.DefaultMarketplaceService;
import org.eclipse.epp.internal.mpc.core.util.URLUtil;
import org.eclipse.epp.internal.mpc.ui.CatalogRegistry;
import org.eclipse.epp.internal.mpc.ui.MarketplaceClientUi;
import org.eclipse.epp.internal.mpc.ui.commands.ImportFavoritesWizardCommand;
import org.eclipse.epp.internal.mpc.ui.commands.MarketplaceWizardCommand;
import org.eclipse.epp.mpc.core.model.INode;
import org.eclipse.osgi.util.NLS;
import org.eclipse.ui.statushandlers.StatusManager;
/**
* Handler for Marketplace URLs. It supports parsing of Marketplace-related URLs through its {@link #handleUri(String)}
* method and will call the appropriate <code>handleXXX</code> methods depending on the URL. Clients can override the
* methods they wish to handle. Default behavior for handle methods is to do nothing unless specified otherwise on the
* handler method.
*
* @author David Green
* @author Benjamin Muskalla
* @author Carsten Reckord
*/
public abstract class MarketplaceUrlHandler {
public static final String DESCRIPTOR_HINT = "org.eclipse.epp.mpc.descriptorHint"; //$NON-NLS-1$
public static final String MPC_INSTALL_URI = "/mpc/install?"; //$NON-NLS-1$
public static final String SITE_SEARCH_URI = "/search/site"; //$NON-NLS-1$
private static final Pattern CONTENT_URL_PATTERN = Pattern.compile("(?:^|/)content/([^/#?]+)"); //$NON-NLS-1$
private static final Pattern NODE_URL_PATTERN = Pattern.compile("(?:^|/)node/([^/#?]+)"); //$NON-NLS-1$
private static final Pattern FAVORITES_URL_PATTERN = Pattern
.compile("(?:^|/)user/([^/#?]+)(/favorites)?([/#?].*)?$"); //$NON-NLS-1$
public static class SolutionInstallationInfo {
private String requestUrl;
private String installId;
private String state;
private CatalogDescriptor catalogDescriptor;
public SolutionInstallationInfo() {
}
protected SolutionInstallationInfo(String installId, String state, CatalogDescriptor catalogDescriptor) {
super();
this.installId = installId;
this.state = state;
this.catalogDescriptor = catalogDescriptor;
}
public void setInstallId(String installId) {
this.installId = installId;
}
public String getInstallId() {
return installId;
}
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void setCatalogDescriptor(CatalogDescriptor catalogDescriptor) {
this.catalogDescriptor = catalogDescriptor;
}
public CatalogDescriptor getCatalogDescriptor() {
return catalogDescriptor;
}
public void setRequestUrl(String requestUrl) {
this.requestUrl = requestUrl;
}
public String getRequestUrl() {
return requestUrl;
}
}
public static final String UTF_8 = "UTF-8"; //$NON-NLS-1$
private static final String PARAM_SPLIT_REGEX = "&"; //$NON-NLS-1$
private static final String EQUALS_REGEX = "="; //$NON-NLS-1$
private static final String MPC_STATE = "mpc_state"; //$NON-NLS-1$
private static final String MPC_INSTALL = "mpc_install"; //$NON-NLS-1$
public static SolutionInstallationInfo createSolutionInstallInfo(String url) {
String installId = null;
String state = null;
Map<String, String> query = parseQuery(url);
if (query != null) {
installId = query.get(MPC_INSTALL);
state = query.get(MPC_STATE);
}
if (installId != null) {
CatalogDescriptor descriptor = findCatalogDescriptor(url, true);
SolutionInstallationInfo info = new SolutionInstallationInfo(installId, state, descriptor);
info.setRequestUrl(url);
return info;
}
return null;
}
private static CatalogDescriptor findCatalogDescriptor(String url, boolean allowUnknown) {
CatalogDescriptor descriptor = CatalogRegistry.getInstance().findCatalogDescriptor(url);
if (descriptor == null && allowUnknown) {
try {
descriptor = new CatalogDescriptor(URLUtil.toURL(url), DESCRIPTOR_HINT);
} catch (MalformedURLException e) {
return null;
}
}
return descriptor;
}
public static String getMPCState(String url) {
Map<String, String> query = parseQuery(url);
return query == null ? null : query.get(MPC_STATE);
}
private static Map<String, String> parseQuery(String url) {
String query;
try {
query = new URL(url).getQuery();
} catch (MalformedURLException e) {
return null;
}
if (query == null) {
return null;
}
Map<String, String> values = new LinkedHashMap<String, String>();
String[] params = query.split(PARAM_SPLIT_REGEX);
for (String param : params) {
String[] keyValue = param.split(EQUALS_REGEX);
if (keyValue.length == 2) {
String key = keyValue[0];
String value = keyValue[1];
values.put(key, value);
}
}
return values;
}
public static boolean isPotentialSolution(String url) {
return url != null && url.contains(MPC_INSTALL);
}
public static boolean isPotentialFavoritesList(String url) {
return url != null && FAVORITES_URL_PATTERN.matcher(url).find();
}
public static void triggerInstall(SolutionInstallationInfo info) {
if (info.getRequestUrl() != null) {
MarketplaceClientUi.getLog().log(
new Status(IStatus.INFO, MarketplaceClientUi.BUNDLE_ID, NLS.bind(
Messages.MarketplaceUrlHandler_performInstallRequest, info.getRequestUrl())));
}
String installId = info.getInstallId();
String mpcState = info.getState();
CatalogDescriptor catalogDescriptor = info.getCatalogDescriptor();
MarketplaceWizardCommand command = new MarketplaceWizardCommand();
command.setSelectedCatalogDescriptor(catalogDescriptor);
try {
if (mpcState != null) {
command.setWizardState(URLDecoder.decode(mpcState, UTF_8));
}
Map<String, Operation> nodeToOperation = new HashMap<String, Operation>();
nodeToOperation.put(URLDecoder.decode(installId, UTF_8), Operation.INSTALL);
command.setOperations(nodeToOperation);
} catch (UnsupportedEncodingException e1) {
throw new IllegalStateException(e1);
}
try {
command.execute(new ExecutionEvent());
} catch (ExecutionException e) {
IStatus status = MarketplaceClientCore.computeStatus(e, Messages.MarketplaceUrlHandler_cannotOpenMarketplaceWizard);
MarketplaceClientUi.handle(status, StatusManager.SHOW | StatusManager.BLOCK | StatusManager.LOG);
}
}
public static void triggerFavorites(String favoritesUrl) {
CatalogDescriptor catalogDescriptor = findCatalogDescriptor(favoritesUrl, true);
ImportFavoritesWizardCommand command = new ImportFavoritesWizardCommand();
command.setSelectedCatalogDescriptor(catalogDescriptor);
command.setFavoritesUrl(favoritesUrl);
try {
command.execute(new ExecutionEvent());
} catch (ExecutionException e) {
IStatus status = MarketplaceClientCore.computeStatus(e,
Messages.MarketplaceUrlHandler_cannotOpenMarketplaceWizard);
MarketplaceClientUi.handle(status, StatusManager.SHOW | StatusManager.BLOCK | StatusManager.LOG);
}
}
public boolean handleUri(String uri) {
if (isPotentialSolution(uri)) {
SolutionInstallationInfo installInfo = createSolutionInstallInfo(uri);
if (installInfo != null) {
return handleInstallRequest(installInfo, uri);
}
}
CatalogDescriptor descriptor = CatalogRegistry.getInstance().findCatalogDescriptor(uri);
if (descriptor == null) {
descriptor = handleUnknownCatalog(uri);
if (descriptor == null) {
return false;
}
}
String baseUri;
try {
baseUri = descriptor.getUrl().toURI().toString();
if (!baseUri.endsWith("/")) { //$NON-NLS-1$
baseUri += '/';
}
} catch (URISyntaxException e) {
// should be unreachable
throw new IllegalStateException(e);
}
if (!uri.startsWith(baseUri)) {
uri = resolve(uri, baseUri, descriptor);
if (!uri.startsWith(baseUri)) {
return false;
}
}
String relativeUri = uri.substring(baseUri.length());
if (relativeUri.startsWith(DefaultMarketplaceService.API_FAVORITES_URI)) {
return handleTopFavorites(descriptor, relativeUri);
} else if (relativeUri.startsWith(DefaultMarketplaceService.API_FEATURED_URI)) {
return handleFeatured(descriptor, relativeUri);
} else if (relativeUri.startsWith(DefaultMarketplaceService.API_NODE_CONTENT_URI)) {
return handleNodeContent(descriptor, relativeUri);
} else if (relativeUri.startsWith(DefaultMarketplaceService.API_NODE_URI)) {
return handleNode(descriptor, relativeUri);
} else if (relativeUri.startsWith(DefaultMarketplaceService.API_POPULAR_URI)) {
return handlePopular(descriptor, relativeUri);
} else if (relativeUri.startsWith(DefaultMarketplaceService.API_RECENT_URI)) {
return handleRecent(descriptor, relativeUri);
} else if (relativeUri.startsWith(DefaultMarketplaceService.API_SEARCH_URI)
|| relativeUri.startsWith(DefaultMarketplaceService.API_SEARCH_URI_FULL)) {
return handleSolrSearch(descriptor, relativeUri);
} else if (relativeUri.startsWith(SITE_SEARCH_URI.substring(1))) {
return handleSiteSearch(descriptor, relativeUri);
} else {
return handleUnknownPath(descriptor, relativeUri);
}
}
/**
* Resolve the given URL against the catalog's base URI. The default implementation changes the HTTP/HTTPS schema to
* match the catalog URI.
*/
protected String resolve(String url, String baseUri, CatalogDescriptor descriptor) {
if (url.startsWith("https:") && baseUri.startsWith("http:")) { //$NON-NLS-1$ //$NON-NLS-2$
url = "http:" + url.substring("https:".length()); //$NON-NLS-1$ //$NON-NLS-2$
} else if (url.startsWith("http:") && baseUri.startsWith("https:")) { //$NON-NLS-1$ //$NON-NLS-2$
url = "https:" + url.substring("http:".length()); //$NON-NLS-1$ //$NON-NLS-2$
}
return url;
}
protected boolean handleUnknownPath(CatalogDescriptor descriptor, String url) {
return false;
}
private boolean handleSolrSearch(CatalogDescriptor descriptor, String url) {
try {
Map<String, String> params = new HashMap<String, String>();
String searchString = parseSearchQuery(descriptor, url, params);
return handleSearch(descriptor, url, searchString, params);
} catch (MalformedURLException e) {
// don't handle malformed URLs
return false;
} catch (URISyntaxException e) {
// don't handle malformed URLs
return false;
}
}
private boolean handleSiteSearch(CatalogDescriptor descriptor, String url) {
try {
Map<String, String> params = new HashMap<String, String>();
String searchString = parseSearchQuery(descriptor, url, params);
// convert queries of this format
// f[0]=im_taxonomy_vocabulary_1:38&f[1]=im_taxonomy_vocabulary_3:31
// to internal solr format
// filter=tid:38 tid:31
StringBuilder filter = new StringBuilder();
for (Iterator<String> i = params.values().iterator(); i.hasNext();) {
String str = i.next();
if (str.startsWith("im_taxonomy_vocabulary_")) { //$NON-NLS-1$
int sep = str.indexOf(':');
if (sep != -1) {
String tid = str.substring(sep + 1);
if (filter.length() > 0) {
filter.append(' ');
}
filter.append(tid);
i.remove();
}
}
}
return handleSearch(descriptor, url, searchString, params);
} catch (MalformedURLException e) {
// don't handle malformed URLs
return false;
} catch (URISyntaxException e) {
// don't handle malformed URLs
return false;
}
}
private String parseSearchQuery(CatalogDescriptor descriptor, String url, Map<String, String> params)
throws URISyntaxException, MalformedURLException {
URI searchUri = new URL(descriptor.getUrl(), url).toURI();
String path = searchUri.getPath();
if (path.endsWith("/")) { //$NON-NLS-1$
path = path.substring(0, path.length() - 1);
}
int sep = path.lastIndexOf('/');
String searchString = path.substring(sep + 1);
String query = searchUri.getQuery();
if (query != null) {
extractParams(query, params);
}
return searchString;
}
protected boolean handleSearch(CatalogDescriptor descriptor, String url, String searchString,
Map<String, String> params) {
return false;
}
private void extractParams(String query, Map<String, String> params) {
final String[] paramStrings = query.split("&"); //$NON-NLS-1$
for (String param : paramStrings) {
final String[] parts = param.split("="); //$NON-NLS-1$
if (parts.length == 2) {
params.put(parts[0], parts[1]);
}
}
}
protected boolean handleRecent(CatalogDescriptor descriptor, String url) {
return false;
}
protected boolean handlePopular(CatalogDescriptor descriptor, String url) {
return false;
}
private boolean handleNode(CatalogDescriptor descriptor, String url) {
Matcher matcher = NODE_URL_PATTERN.matcher(url);
String id = null;
if (matcher.find()) {
id = matcher.group(1);
}
Node node = new Node();
node.setId(id);
return handleNode(descriptor, url, node);
}
private boolean handleNodeContent(CatalogDescriptor descriptor, String url) {
Matcher matcher = CONTENT_URL_PATTERN.matcher(url);
String title = null;
if (matcher.find()) {
title = matcher.group(1);
}
Node node = new Node();
node.setUrl(url);
if (title != null) {
String base = descriptor.getUrl().toExternalForm();
if (!base.endsWith("/")) { //$NON-NLS-1$
base += "/"; //$NON-NLS-1$
}
int titleEnd = matcher.end();
if (titleEnd > -1) {
//clean the url of other query parameters
node.setUrl(base + url.substring(0, titleEnd));
} else {
//unknown format, leave as-is
node.setUrl(base + url);
}
}
return handleNode(descriptor, url, node);
}
protected boolean handleNode(CatalogDescriptor descriptor, String url, INode node) {
return false;
}
private boolean handleFeatured(CatalogDescriptor descriptor, String url) {
Matcher matcher = Pattern.compile("(?:^|/)featured/(\\d+)(?:,(\\d+))?").matcher(url); //$NON-NLS-1$
String cat = null;
String market = null;
if (matcher.find()) {
cat = matcher.group(1);
if (matcher.groupCount() > 1) {
market = matcher.group(2);
}
}
return handleFeatured(descriptor, url, cat, market);
}
protected boolean handleFeatured(CatalogDescriptor descriptor, String url, String category, String market) {
return false;
}
protected boolean handleTopFavorites(CatalogDescriptor descriptor, String url) {
return false;
}
/**
* Called if no known {@link CatalogDescriptor} is registered for the given URL. Clients may override to return a
* custom {@link CatalogDescriptor} or to perform additional lookup of registered catalogs.
* <p>
* The default implementation looks for catalogs registered for the same host but a different schema - if the url
* uses http, a catalog with an equivalent https url is searched and vice versa.
*/
protected CatalogDescriptor handleUnknownCatalog(String url) {
if (url.startsWith("https:")) { //$NON-NLS-1$
url = "http:" + url.substring("https:".length()); //$NON-NLS-1$ //$NON-NLS-2$
return CatalogRegistry.getInstance().findCatalogDescriptor(url);
} else if (url.startsWith("http:")) { //$NON-NLS-1$
url = "https:" + url.substring("http:".length()); //$NON-NLS-1$ //$NON-NLS-2$
return CatalogRegistry.getInstance().findCatalogDescriptor(url);
}
return null;
}
protected boolean handleInstallRequest(SolutionInstallationInfo installInfo, String url) {
return false;
}
}