/******************************************************************************* * Copyright (c) 2010, 2012 Tasktop Technologies 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 *******************************************************************************/ package org.eclipse.mylyn.commons.repositories.core; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Proxy; import java.net.SocketAddress; import java.net.URI; import java.net.URISyntaxException; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.PlatformObject; import org.eclipse.core.runtime.Status; import org.eclipse.mylyn.commons.core.StatusHandler; import org.eclipse.mylyn.commons.core.net.NetUtil; import org.eclipse.mylyn.commons.repositories.core.RepositoryLocationChangeEvent.Type; import org.eclipse.mylyn.commons.repositories.core.auth.AuthenticationCredentials; import org.eclipse.mylyn.commons.repositories.core.auth.AuthenticationRequest; import org.eclipse.mylyn.commons.repositories.core.auth.AuthenticationType; import org.eclipse.mylyn.commons.repositories.core.auth.ICredentialsStore; import org.eclipse.mylyn.commons.repositories.core.auth.UserCredentials; import org.eclipse.mylyn.internal.commons.repositories.core.CredentialsFactory; import org.eclipse.mylyn.internal.commons.repositories.core.InMemoryCredentialsStore; import org.eclipse.mylyn.internal.commons.repositories.core.LocationService; import org.eclipse.mylyn.internal.commons.repositories.core.RepositoriesCoreInternal; /** * @author Steffen Pingel */ // FIXME add synchronization public class RepositoryLocation extends PlatformObject { private static boolean flushCredentialsErrorLogged; public static final String PROPERTY_ID = "id"; //$NON-NLS-1$ public static final String PROPERTY_LABEL = "label"; //$NON-NLS-1$ public static final String PROPERTY_OFFLINE = "org.eclipse.mylyn.tasklist.repositories.offline"; //$NON-NLS-1$ public static final String PROPERTY_PROXY_HOST = "org.eclipse.mylyn.repositories.proxy.host"; //$NON-NLS-1$ public static final String PROPERTY_PROXY_PORT = "org.eclipse.mylyn.repositories.proxy.port"; //$NON-NLS-1$ public static final String PROPERTY_PROXY_USEDEFAULT = "org.eclipse.mylyn.repositories.proxy.usedefault"; //$NON-NLS-1$ public static final String PROPERTY_URL = "url"; //$NON-NLS-1$ public static final String PROPERTY_USERNAME = "org.eclipse.mylyn.repositories.username"; //$NON-NLS-1$ private static final String ENABLED = ".enabled"; //$NON-NLS-1$ private static Map<String, String> createDefaultProperties() { Map<String, String> defaultProperties = new HashMap<String, String>(); defaultProperties.put(PROPERTY_PROXY_USEDEFAULT, Boolean.TRUE.toString()); return defaultProperties; } private ICredentialsStore credentialsStore; // transient private IStatus errorStatus = null; private final Map<String, String> properties = new LinkedHashMap<String, String>(); private final List<PropertyChangeListener> propertyChangeListeners = new CopyOnWriteArrayList<PropertyChangeListener>(); private final List<IRepositoryLocationChangeListener> repositoryLocationChangeListeners = new CopyOnWriteArrayList<IRepositoryLocationChangeListener>(); private ILocationService service; private final boolean workingCopy; public RepositoryLocation() { this(createDefaultProperties(), LocationService.getDefault(), false); } public RepositoryLocation(String url) { this(); setUrl(url); } public RepositoryLocation(Map<String, String> properties) { this(properties, LocationService.getDefault(), true); } public RepositoryLocation(Map<String, String> properties, ILocationService service, boolean workingCopy) { this.properties.putAll(properties); this.service = service; this.workingCopy = workingCopy; if (this.properties.get(PROPERTY_ID) == null) { this.properties.put(RepositoryLocation.PROPERTY_ID, UUID.randomUUID().toString()); } } public RepositoryLocation(RepositoryLocation source) { this(source.getProperties(), source.getService(), true); } public void addPropertyChangeListener(PropertyChangeListener listener) { propertyChangeListeners.add(listener); } public void apply(RepositoryLocation location) { String oldId = getProperty(PROPERTY_ID); ICredentialsStore oldCredentialsStore = null; if (oldId != null) { oldCredentialsStore = getCredentialsStore(); } // merge properties HashSet<String> removed = new HashSet<String>(properties.keySet()); removed.removeAll(location.properties.keySet()); for (Map.Entry<String, String> entry : location.properties.entrySet()) { setProperty(entry.getKey(), entry.getValue()); } for (String key : removed) { setProperty(key, null); } String newId = getProperty(PROPERTY_ID); if (newId != null) { // migrate credentials if url has changed ICredentialsStore newCredentialsStore = getCredentialsStore(); if (!newId.equals(oldId)) { if (oldCredentialsStore != null) { oldCredentialsStore.copyTo(newCredentialsStore); oldCredentialsStore.clear(); } } // merge credentials if (location.getCredentialsStore() instanceof InMemoryCredentialsStore) { ((InMemoryCredentialsStore) location.getCredentialsStore()).copyTo(newCredentialsStore); } // persist changes try { newCredentialsStore.flush(); } catch (IOException e) { if (!flushCredentialsErrorLogged) { flushCredentialsErrorLogged = true; StatusHandler.log(new Status( IStatus.ERROR, RepositoriesCoreInternal.ID_PLUGIN, "Unexpected error occured while flushing credentials. Credentials may not have been saved.", e)); //$NON-NLS-1$ } } } fireRepositoryLocationChangeEvent(Type.ALL); } public void clearCredentials() { getCredentialsStore().clear(); } public boolean getBooleanPropery(String key) { String value = getProperty(key); return value != null && Boolean.parseBoolean(value); } public <T extends AuthenticationCredentials> T getCredentials(AuthenticationType<T> authType) { return getCredentials(authType, true); } public <T extends AuthenticationCredentials> T getCredentials(AuthenticationType<T> authType, boolean loadSecrets) { String prefix = authType.getKey(); if (getBooleanPropery(prefix + ENABLED)) { if (getId() == null) { // can't determine location of credentials return null; } return CredentialsFactory.create(authType.getCredentialsType(), getCredentialsStore(), prefix, loadSecrets); } return null; } public ICredentialsStore getCredentialsStore() { if (credentialsStore == null) { return getService().getCredentialsStore(getId()); } return credentialsStore; } public String getId() { String id = getProperty(PROPERTY_ID); if (id == null) { throw new IllegalStateException("Repository ID is not set"); //$NON-NLS-1$ } return id; } /** * @return the URL if the label property is not set */ public String getLabel() { String label = properties.get(PROPERTY_LABEL); if (label != null && label.length() > 0) { return label; } else { return getUrl(); } } public Map<String, String> getProperties() { return new LinkedHashMap<String, String>(this.properties); } public String getProperty(String name) { return this.properties.get(name); } public Proxy getProxy() { if (Boolean.parseBoolean(getProperty(PROPERTY_PROXY_USEDEFAULT))) { return null; } String proxyHost = getProperty(PROPERTY_PROXY_HOST); String proxyPort = getProperty(PROPERTY_PROXY_PORT); if (proxyHost != null && proxyHost.length() > 0 && proxyPort != null) { try { int proxyPortNum = Integer.parseInt(proxyPort); UserCredentials credentials = getCredentials(AuthenticationType.PROXY); if (credentials != null) { return NetUtil.createProxy(proxyHost, proxyPortNum, credentials.getUserName(), credentials.getPassword(), credentials.getDomain()); } else { return NetUtil.createProxy(proxyHost, proxyPortNum); } } catch (NumberFormatException e) { StatusHandler.log(new Status(IStatus.ERROR, RepositoriesCoreInternal.ID_PLUGIN, 0, "Error occured while configuring proxy. Invalid port \"" //$NON-NLS-1$ + proxyPort + "\" specified.", e)); //$NON-NLS-1$ } } return null; } // FIXME e3.5 replace with 3.5 proxy API public Proxy getProxyForHost(String host, String proxyType) { Proxy proxy = getProxy(); if (proxy != null) { return proxy; } return getService().getProxyForHost(host, proxyType); } public ILocationService getService() { return service; } public IStatus getStatus() { return errorStatus; } public String getUrl() { return getProperty(PROPERTY_URL); } /** * The username is cached since it needs to be retrieved frequently (e.g. for Task List decoration). */ public String getUserName() { return getProperty(PROPERTY_USERNAME); } public boolean hasProperty(String name) { String value = getProperty(name); return value != null && value.trim().length() > 0; } /** * Returns true if a normalized form of <code>url</code> matches the URL of this location. */ public boolean hasUrl(String url) { Assert.isNotNull(url); String myUrl = getUrl(); if (myUrl == null) { return false; } try { return new URI(url + "/").normalize().equals(new URI(myUrl + "/").normalize()); //$NON-NLS-1$//$NON-NLS-2$ } catch (URISyntaxException e) { return false; } } public boolean isOffline() { return Boolean.parseBoolean(getProperty(PROPERTY_OFFLINE)); } public boolean isWorkingCopy() { return workingCopy; } public void removePropertyChangeListener(PropertyChangeListener listener) { propertyChangeListeners.remove(listener); } public <T extends AuthenticationCredentials> void removeCredentials(AuthenticationType<T> authType, T credentials) { String prefix = authType.getKey(); credentials.clear(getCredentialsStore(), prefix); } public void removeProperty(String key) { setProperty(key, null); } /** * Requests credentials. This may block and prompt to enter credentials. * * @param request * the authentication request * @param monitor * the progress monitor * @return the entered credentials * @see ILocationService#requestCredentials(AuthenticationRequest, IProgressMonitor) */ public <T extends AuthenticationCredentials> T requestCredentials( AuthenticationRequest<AuthenticationType<T>> request, IProgressMonitor monitor) { return getService().requestCredentials(request, monitor); } public <T extends AuthenticationCredentials> void setCredentials(AuthenticationType<T> authType, T credentials) { String prefix = authType.getKey(); if (credentials == null) { setProperty(prefix + ENABLED, String.valueOf(false)); } else { setProperty(prefix + ENABLED, String.valueOf(true)); credentials.save(getCredentialsStore(), prefix); } fireRepositoryLocationChangeEvent(Type.CREDENTIALS); } public void setCredentialsStore(ICredentialsStore credentialsStore) { this.credentialsStore = credentialsStore; } public void setIdPreservingCredentialsStore(String id) { Assert.isNotNull(id); ICredentialsStore store = getCredentialsStore(); setProperty(RepositoryLocation.PROPERTY_ID, id); if (this.credentialsStore == null) { setCredentialsStore(store); } } public void setLabel(String label) { setProperty(PROPERTY_LABEL, label); } public void setOffline(boolean offline) { properties.put(PROPERTY_OFFLINE, String.valueOf(offline)); } public void setProperty(String key, String newValue) { validatePropertyChange(key, newValue); String oldValue = this.properties.get(key); if (hasChanged(oldValue, newValue)) { this.properties.put(key.intern(), (newValue != null) ? newValue.intern() : null); handlePropertyChange(key, oldValue, newValue); } } public void validatePropertyChange(String key, String newValue) { Assert.isNotNull(key); if (key.equals(RepositoryLocation.PROPERTY_ID) && newValue == null) { throw new IllegalArgumentException("The ID property must not be null"); //$NON-NLS-1$ } } public void setProxy(Proxy proxy) { if (proxy == null) { setProperty(PROPERTY_PROXY_USEDEFAULT, Boolean.toString(true)); } else { SocketAddress address = proxy.address(); if (address instanceof InetSocketAddress) { setProperty(PROPERTY_PROXY_HOST, ((InetSocketAddress) address).getHostName()); setProperty(PROPERTY_PROXY_PORT, Integer.toString(((InetSocketAddress) address).getPort())); setProperty(PROPERTY_PROXY_USEDEFAULT, Boolean.toString(false)); } else { throw new IllegalArgumentException("Invalid proxy address"); //$NON-NLS-1$ } } fireRepositoryLocationChangeEvent(Type.PROYX); } public void setService(ILocationService service) { this.service = service; } public void setStatus(IStatus errorStatus) { this.errorStatus = errorStatus; } public void setUrl(String url) { setProperty(PROPERTY_URL, url); } public void setUserName(String userName) { setProperty(PROPERTY_USERNAME, userName); } @Override public String toString() { return getLabel(); } private void handlePropertyChange(String key, Object old, Object value) { if (PROPERTY_ID.equals(key)) { credentialsStore = null; } firePropertyChangeEvent(key, old, value); } private void firePropertyChangeEvent(String key, Object old, Object value) { PropertyChangeEvent event = new PropertyChangeEvent(this, key, old, value); for (PropertyChangeListener listener : propertyChangeListeners) { listener.propertyChange(event); } } private void fireRepositoryLocationChangeEvent(RepositoryLocationChangeEvent.Type type) { RepositoryLocationChangeEvent event = new RepositoryLocationChangeEvent(this, type); for (IRepositoryLocationChangeListener listener : repositoryLocationChangeListeners) { listener.repositoryChanged(event); } } private boolean hasChanged(Object oldValue, Object newValue) { return oldValue != null && !oldValue.equals(newValue) || oldValue == null && newValue != null; } }