/******************************************************************************* * 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 *******************************************************************************/ package org.eclipse.epp.internal.mpc.core.service; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.CopyOnWriteArrayList; import org.eclipse.core.runtime.CoreException; import org.eclipse.epp.internal.mpc.core.MarketplaceClientCore; import org.eclipse.epp.internal.mpc.core.util.ServiceUtil; import org.eclipse.epp.mpc.core.service.IMarketplaceStorageService; import org.eclipse.equinox.security.storage.ISecurePreferences; import org.eclipse.equinox.security.storage.StorageException; import org.eclipse.osgi.util.NLS; import org.eclipse.userstorage.IBlob; import org.eclipse.userstorage.IStorage; import org.eclipse.userstorage.IStorageService; import org.eclipse.userstorage.StorageFactory; import org.eclipse.userstorage.internal.StorageService; import org.eclipse.userstorage.oauth.EclipseOAuthCredentialsProvider; import org.eclipse.userstorage.spi.Credentials; import org.eclipse.userstorage.spi.ICredentialsProvider; import org.eclipse.userstorage.spi.ISettings; import org.eclipse.userstorage.util.FileStorageCache; import org.eclipse.userstorage.util.Settings; import org.osgi.framework.BundleContext; @SuppressWarnings("restriction") public class MarketplaceStorageService implements IMarketplaceStorageService { private static final String DEFAULT_STORAGE_SERVICE_NAME = Messages.MarketplaceStorageService_defaultStorageServiceName; static final String DEFAULT_APPLICATION_TOKEN = "MZ04RMOpksKN5GpxKXafq2MSjSP"; //$NON-NLS-1$ private static final String[] MIGRATE_SECURE_STORAGE_KEYS = { "username", "password", "termsOfUseAgreed" }; //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ private String applicationToken = DEFAULT_APPLICATION_TOKEN; private URI serviceUri; private StorageFactory storageFactory; private IStorage storage; private IStorageService.Dynamic customStorageService; private List<LoginListener> loginListeners; private EclipseOAuthCredentialsProvider credentialsProvider; private StorageConfigurer configurer; public URI getServiceUri() { return serviceUri; } public synchronized void setServiceUri(URI serviceUri) { if ((this.serviceUri == null && serviceUri != null) || (this.serviceUri != null && !this.serviceUri.equals(serviceUri))) { this.serviceUri = serviceUri; storageFactory = null; storage = null; } } public synchronized StorageFactory getStorageFactory() { if (storageFactory == null) { storageFactory = createStorageFactory(); } return storageFactory; } public synchronized void setStorageFactory(StorageFactory storageFactory) { this.storageFactory = storageFactory; } protected StorageFactory createStorageFactory() { if (serviceUri == null) { return StorageFactory.DEFAULT; } Map<String, String> settingsMap = new HashMap<String, String>(); settingsMap.put(applicationToken, serviceUri.toString()); ISettings storageFactorySettings = new Settings.MemorySettings(settingsMap); return new StorageFactory(storageFactorySettings); } protected IStorage createStorage() { IStorage storage = getStorageFactory().create(applicationToken, new FileStorageCache.SingleApplication(this.applicationToken)); try { configurer = StorageConfigurer.get(); configurer.configure(storage); configurer.setInteractive(storage, false); } catch (CoreException e) { configurer = null; MarketplaceClientCore.error(e); } return storage; } public synchronized IStorage getStorage() { if (storage == null) { storage = createStorage(); } return storage; } public void setStorage(IStorage storage) { this.storage = storage; } public IBlob getBlob(String key) { return getStorage().getBlob(key); } public String getRegisteredUser() { Credentials credentials = getStorageCredentials(); return credentials == null ? null : credentials.getUsername(); } public <T> T runWithLogin(Callable<T> c) throws Exception { String oldUser = getCurrentUser(); try { T result = configurer != null ? configurer.runWithLogin(storage, c) : c.call(); return result; } finally { String newUser = getCurrentUser();//TODO the OAuth token might change with the user staying the same - this would cause an unnecessary refresh event notifyLoginChanged(oldUser, newUser); } } private void notifyLoginChanged(String oldUser, String newUser) { if ((newUser == null && oldUser != null) || (newUser != null && !newUser.equals(oldUser))) { List<LoginListener> loginListeners = this.loginListeners; if (loginListeners != null && !loginListeners.isEmpty()) { for (LoginListener loginListener : loginListeners) { loginListener.loginChanged(oldUser, newUser); } } } } public synchronized void addLoginListener(LoginListener listener) { if (loginListeners == null) { loginListeners = new CopyOnWriteArrayList<LoginListener>(); } if (!loginListeners.contains(listener)) { loginListeners.add(listener); } } public synchronized void removeLoginListener(LoginListener listener) { if (loginListeners != null) { loginListeners.remove(listener); } } private String getCurrentUser() { Credentials credentials = getStorageCredentials(); if (credentials != null && credentials.getPassword() != null) { return credentials.getUsername(); } return null; } private Credentials getStorageCredentials() { ICredentialsProvider provider = getStorage().getCredentialsProvider(); if (provider == null) { return null; } IStorageService service = getStorage().getService(); //return provider.hasCredentials(service) ? provider.getCredentials(service) : null; return ((StorageService) service).getCredentials(); } public void activate(BundleContext context, Map<?, ?> properties) { Object serviceUrlValue = ServiceUtil.getOverridablePropertyValue(properties, STORAGE_SERVICE_URL_PROPERTY); if (serviceUrlValue != null) { URI serviceUri = URI.create(serviceUrlValue.toString()); String serviceName = getProperty(properties, STORAGE_SERVICE_NAME_PROPERTY, DEFAULT_STORAGE_SERVICE_NAME); IStorageService registered = registerStorageService(serviceUri, serviceName); setServiceUri(registered.getServiceURI()); } String applicationToken = getProperty(properties, APPLICATION_TOKEN_PROPERTY, DEFAULT_APPLICATION_TOKEN); this.applicationToken = applicationToken; } private IStorageService registerStorageService(URI serviceUri, String serviceName) { customStorageService = null; URI normalizedUri = normalizePath(serviceUri); URI denormalizedUri = normalizePath(serviceUri, false); IStorageService service = IStorageService.Registry.INSTANCE.getService(normalizedUri); IStorageService extraService = IStorageService.Registry.INSTANCE.getService(denormalizedUri); if (service == null) { if (extraService != null) { return extraService; } customStorageService = IStorageService.Registry.INSTANCE.addService(serviceName, normalizedUri); service = customStorageService; return service; } if (extraService != null && extraService != service) { service = cleanupExtraStorageService(service, extraService); } return service; } private static IStorageService cleanupExtraStorageService(IStorageService service, IStorageService extraService) { IStorageService.Dynamic removeDynamic; IStorageService keepService; if (extraService instanceof IStorageService.Dynamic) { removeDynamic = (IStorageService.Dynamic) extraService; keepService = service; } else if (service instanceof IStorageService.Dynamic) { removeDynamic = (IStorageService.Dynamic) service; keepService = extraService; } else { return service; } if (removeDynamic instanceof StorageService && keepService instanceof StorageService) { StorageService removeImpl = (StorageService) removeDynamic; StorageService keepImpl = (StorageService) keepService; ISecurePreferences removeSecurePreferences = removeImpl.getSecurePreferences(); ISecurePreferences keepSecurePreferences = keepImpl.getSecurePreferences(); try { copySecurePreferences(removeSecurePreferences, keepSecurePreferences); } catch (Exception e) { MarketplaceClientCore.error(NLS.bind("Failed to migrate secure storage values from {0} to {1}", removeDynamic.getServiceURI(), keepService.getServiceURI()), e); } } removeDynamic.remove(); return keepService; } private static void copySecurePreferences(ISecurePreferences source, ISecurePreferences target) throws StorageException, IOException { Set<String> sourceKeys = new HashSet<String>(Arrays.asList(source.keys())); Set<String> targetKeys = new HashSet<String>(Arrays.asList(target.keys())); boolean changed = false; for (String key : MIGRATE_SECURE_STORAGE_KEYS) { if (sourceKeys.contains(key) && !targetKeys.contains(key)) { boolean encrypted = source.isEncrypted(key); target.put(key, source.get(key, null), encrypted); changed = true; } } if (changed) { target.flush(); } } private static URI normalizePath(URI uri) { return normalizePath(uri, true); } private static URI normalizePath(URI uri, boolean trailingSlash) { if (uri.isOpaque()) { return uri; } String path = uri.getPath(); String normalizedPath; if (path == null) { if (trailingSlash) { normalizedPath = "/"; //$NON-NLS-1$ } else { return uri; } } else if (path.endsWith("/")) { //$NON-NLS-1$ if (trailingSlash) { return uri; } else { normalizedPath = path.length() == 1 ? null : path.substring(0, path.length() - 1); } } else { if (trailingSlash) { normalizedPath = path + "/"; //$NON-NLS-1$ } else { return uri; } } try { return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), normalizedPath, uri.getQuery(), uri.getFragment()); } catch (URISyntaxException e) { return uri; } } private static String getProperty(Map<?, ?> properties, String key, String defaultValue) { Object value = properties.get(key); if (value == null) { return defaultValue; } return value.toString(); } public void deactivate() { if (customStorageService != null) { customStorageService.remove(); } customStorageService = null; } }