/*******************************************************************************
* Copyright (c) 2009 IBM Corporation 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:
* IBM Corporation - initial API and implementation
* Zend Technologies
*******************************************************************************/
package org.eclipse.php.internal.server.core.manager;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.*;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.preferences.DefaultScope;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.IScopeContext;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.php.internal.core.util.preferences.IXMLPreferencesStorable;
import org.eclipse.php.internal.core.util.preferences.XMLPreferencesReader;
import org.eclipse.php.internal.core.util.preferences.XMLPreferencesWriter;
import org.eclipse.php.internal.server.core.Activator;
import org.eclipse.php.internal.server.core.Logger;
import org.eclipse.php.internal.server.core.Server;
import org.osgi.service.prefs.BackingStoreException;
/**
* A manager for PHP Servers.
*
* @author shalom
*/
public class ServersManager implements PropertyChangeListener, IAdaptable {
private class ServersUpgrade extends Job {
private boolean upgraded = true;
private ServersUpgrade() {
super(Messages.ServersManager_Upgrading_server_configurations);
}
@SuppressWarnings("unchecked")
void check(Map<String, Object> serverMap) {
Map<String, String> attributes = (Map<String, String>) serverMap.get(Server.SERVER_ELEMENT);
// Upgrade servers with unique ID element
if (!attributes.containsKey(Server.UNIQUE_ID))
upgraded = false;
}
void perform() {
if (!upgraded)
schedule();
}
@Override
protected IStatus run(IProgressMonitor monitor) {
monitor.beginTask(Messages.ServersManager_Saving_upgraded_configurations, IProgressMonitor.UNKNOWN);
save();
monitor.done();
return Status.OK_STATUS;
}
}
private static final class NoneServer extends Server {
private NoneServer() throws MalformedURLException {
super("<none>", "<none>", "http://<none>", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
}
/** Servers preferences key */
public static final String SERVERS_PREFERENCES_KEY = "phpServers"; //$NON-NLS-1$
/** Default server name preferences key */
public static final String DEFAULT_SERVER_PREFERENCES_KEY = "defaultPHPServer"; //$NON-NLS-1$
// defaultServersMap holds a mapping between an IProject to a Server.
// We take advantage of the NULL wrapping to indicate that a null values
// will also
// be mapped to a server (the workspace server).
private Map<IProject, Server> defaultServersMap = new HashMap<IProject, Server>();
// Holds a server name to Server instance mapping.
private Map<String, Server> servers;
private List<IServersManagerListener> listeners;
private static ServersManager instance;
private static final String NODE_QUALIFIER = Activator.PLUGIN_ID + ".phpServersPrefs"; //$NON-NLS-1$
private static final String BASE_URL = "http://localhost"; //$NON-NLS-1$
public static final String DEFAULT_SERVER_NAME = "Default PHP Web Server"; //$NON-NLS-1$
public static final synchronized ServersManager getInstance() {
if (instance == null) {
instance = new ServersManager();
}
return instance;
}
private ServersManager() {
servers = new LinkedHashMap<String, Server>();
listeners = new ArrayList<IServersManagerListener>();
loadServers();
}
/**
* Adds an IServersManagerListener.
*
* @param listener
*/
public static void addManagerListener(IServersManagerListener listener) {
List<IServersManagerListener> listeners = getInstance().listeners;
if (!listeners.contains(listener)) {
listeners.add(listener);
}
}
/**
* Removes an IServersManagerListener.
*
* @param listener
*/
public static void removeManagerListener(IServersManagerListener listener) {
getInstance().listeners.remove(listener);
}
/**
* Adds a Server to the manager. If a server with the same name exists in
* the manager, the existing server will be overidden with the new one and
* event notifications will be fired for the removal and for the addition.
* Note: The added server is not saved into the preferences until the
* {@link #save()} is called.
*
* @param server
* A Server
* @see #save()
*/
public static void addServer(Server server) {
if (server == null) {
return;
}
ServersManager manager = getInstance();
Server oldValue = ServersManager.getServer(server);
if (server != oldValue) {
manager.servers.remove(oldValue.getName());
oldValue.removePropertyChangeListener(manager);
ServerManagerEvent event = new ServerManagerEvent(ServerManagerEvent.MANAGER_EVENT_REMOVED, oldValue);
manager.fireEvent(event);
}
oldValue = (Server) manager.servers.put(server.getName(), server);
if (oldValue != null) {
oldValue.removePropertyChangeListener(manager);
ServerManagerEvent event = new ServerManagerEvent(ServerManagerEvent.MANAGER_EVENT_REMOVED, oldValue);
manager.fireEvent(event);
}
Server defaultServer = getDefaultServer(null);
if (defaultServer == null || isNoneServer(defaultServer)) {
// Set workspace default if there is no any
setDefaultServer(null, server);
}
ServerManagerEvent event = new ServerManagerEvent(ServerManagerEvent.MANAGER_EVENT_ADDED, server);
manager.fireEvent(event);
// Register the manager as a Server lister to get nofitications about
// attribute changes.
server.addPropertyChangeListener(manager);
}
/**
* Check if specified PHP server is a 'none' server.
*
* @param server
* @return <code>true</code> if specified server is 'none'; otherwise return
* <code>false</code>
*/
public static boolean isNoneServer(Server server) {
return server instanceof NoneServer;
}
/**
* Removes a Server from the manager. In case that that given server is set
* to be the default server for a project, the project will be set with the
* workspace default server.
*
* @param serverName
* The name of the server.
* @return The removed Server; null if non was found to be removed.
*/
public static Server removeServer(String serverName) {
// Do it the long way...
// Check if the removed server is the workspace default, and if so,
// replace the default to the
// first in the list.
ServersManager manager = ServersManager.getInstance();
Server workspaceDefault = getDefaultServer(null);
Server removedServer = (Server) manager.servers.remove(serverName);
if (removedServer == null) {
// if the name is not existing, just quit.
return null;
}
if (workspaceDefault == removedServer) {
// If the workspace default server is the same as the one we wish to
// remove,
// we should replace it.
Server[] servers = getServers();
if (servers.length == 1) {
setDefaultServer(null, servers[0]);
} else if (servers.length > 1) {
// Take second one as first one is '<none>'
setDefaultServer(null, servers[1]);
}
}
// Check that if any one of the mapped projects holds a reference to the
// removed server.
// If so, replace it with the new default server.
Object[] keys = manager.defaultServersMap.keySet().toArray();
for (Object element : keys) {
if (removedServer == manager.defaultServersMap.get(element)) {
setDefaultServer((IProject) element, workspaceDefault);
}
}
if (removedServer != null) {
// Fire the event for the removal
removedServer.removePropertyChangeListener(manager);
ServerManagerEvent event = new ServerManagerEvent(ServerManagerEvent.MANAGER_EVENT_REMOVED, removedServer);
manager.fireEvent(event);
}
return removedServer;
}
/**
* Returns a Server from the manager.
*
* @param serverName
* The name of the server.
* @return The Server; null if non was found.
*/
public static Server getServer(String serverName) {
ServersManager manager = getInstance();
return (Server) manager.servers.get(serverName);
}
public static Server getServer(Server oldServer) {
ServersManager manager = getInstance();
for (Iterator<Server> iterator = manager.servers.values().iterator(); iterator.hasNext();) {
Server server = iterator.next();
if (server.getBaseURL().equals(oldServer.getBaseURL())) {
return server;
}
}
return oldServer;
}
/**
* Finds and returns a server with given unique ID.
*
* @param serverId
* @return server with given unique ID
*/
public static Server findServer(String serverId) {
ServersManager manager = getInstance();
for (Iterator<Server> iterator = manager.servers.values().iterator(); iterator.hasNext();) {
Server server = iterator.next();
if (server.getUniqueId().equals(serverId)) {
return server;
}
}
return null;
}
/**
* Tries to find and return a server that corresponds to given URL
*
* @param urlString
* URL text form
* @return server that corresponds to given URL or <code>null</code> if
* there is no match
*/
public static Server findByURL(String urlString) {
if (urlString == null) {
return null;
}
List<Server> matches = null;
URL url;
try {
url = new URL(urlString);
} catch (MalformedURLException ex) {
Logger.logException("Find server match by URL error: Invalid URL.", //$NON-NLS-1$
ex);
return null;
}
String urlHostAddress = url.getHost();
try {
InetAddress urlHostInetAddress = InetAddress.getByName(urlHostAddress);
// Resolve it to IP address
urlHostAddress = urlHostInetAddress.getHostAddress();
} catch (UnknownHostException e) {
// may not exists - ignore
}
// Find all servers that match given URL host address
List<Server> matchedByHost = new ArrayList<Server>();
for (Server server : getServers()) {
if (isNoneServer(server))
continue;
String serverHost = server.getHost();
try {
InetAddress serverHostAddress = InetAddress.getByName(serverHost);
if (urlHostAddress.equals(serverHostAddress.getHostAddress())) {
matchedByHost.add(server);
}
} catch (UnknownHostException e) {
// ignore
}
}
matches = matchedByHost;
if (matches.isEmpty())
return null;
// Filter out the servers by URL protocol
List<Server> matchedByProtocol = new ArrayList<Server>();
for (Server server : matches) {
if (url.getProtocol().equals(server.getRootURL().getProtocol())) {
matchedByProtocol.add(server);
}
}
if (!matchedByProtocol.isEmpty())
matches = matchedByProtocol;
// Filter out the servers by URL port
List<Server> matchedByPort = new ArrayList<Server>();
int urlPort = url.getPort() != -1 ? url.getPort() : Server.DEFAULT_HTTP_PORT;
for (Server server : matches) {
if (urlPort == server.getPort()) {
matchedByPort.add(server);
}
}
if (!matchedByPort.isEmpty())
matches = matchedByPort;
// Return first match
return matches.get(0);
}
/**
* Returns all the Servers that are managed by this manager.
*
* @return An array of Servers.
*/
public static Server[] getServers() {
ServersManager manager = getInstance();
Collection<Server> values = manager.servers.values();
Server[] servers = new Server[values.size()];
values.toArray(servers);
return servers;
}
/**
* Returns the dafault debug server.
*
* @return
*/
public static Server getDefaultServer(IProject project) {
ServersManager manager = getInstance();
// Try to get it from the memory first.
Server server = (Server) manager.defaultServersMap.get(project);
if (project != null) {
/*
* In case that the project is not null, check that we have
* project-specific settings for it. Otherwise, map it to the
* workspace default server.
*/
IScopeContext[] preferenceScopes = createPreferenceScopes(project);
String projectSpecificServer = preferenceScopes[0].getNode(NODE_QUALIFIER)
.get(DEFAULT_SERVER_PREFERENCES_KEY, (String) null);
if (projectSpecificServer == null) {
// We do not have a project specific setting for this project.
// Map it to the default workspace server.
manager.defaultServersMap.put(project, manager.defaultServersMap.get(null));
server = (Server) manager.defaultServersMap.get(null);
}
}
/*
* If the server was no found in our hash, try to load it from the
* preferences. This part of code should only happen one time when the
* first call for the getDefaultServer. Once it's done, there is no
* reason to re-load the servers definitions from the preferences (XML).
*/
if (server == null) {
IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(Activator.PLUGIN_ID);
String serverName = null;
if (project == null) {
// Get the default workspace server.
serverName = preferences.get(DEFAULT_SERVER_PREFERENCES_KEY, null);
} else {
// Get the projects' default server
IScopeContext[] preferenceScopes = createPreferenceScopes(project);
serverName = preferenceScopes[0].getNode(NODE_QUALIFIER).get(DEFAULT_SERVER_PREFERENCES_KEY, null);
if (serverName == null) {
// No project server - take the workspace one
serverName = preferences.get(DEFAULT_SERVER_PREFERENCES_KEY, null);
}
}
if (serverName != null && !"".equals(serverName)) { //$NON-NLS-1$
server = (Server) manager.servers.get(serverName);
/*
* Map this server as the default for the project (if not null)
* or for the workspace (when the project is null).
*/
manager.defaultServersMap.put(project, server);
}
}
// fixed bug 197579 - in case of no default server mark the first server
// as default
if (server == null) {
if (manager.servers.size() > 0) {
server = (Server) manager.servers.values().iterator().next();
setDefaultServer(null, server);
}
}
return server;
}
/**
* Sets the default debug server. In case that the given project is null,
* the setting if for the workspace. In case that the given server is null,
* the preferences value stored for the given project will be removed.
*
* @param project
* A project to assign to a default server.
* @param server
* A default server for the given project.
*/
public static void setDefaultServer(IProject project, Server server) {
ServersManager manager = getInstance();
/*
* Get the default server for the given project. In case that we need to
* set a new server for the project, make sure we save it as well.
*/
Server defaultProjectServer = (Server) manager.defaultServersMap.get(project);
if (server != defaultProjectServer) {
manager.defaultServersMap.put(project, server);
}
manager.innerSaveDefaultServer(project, server);
}
/**
* Sets the default debug server. In case that the given project is null,
* the setting if for the workspace.
*
* @param project
* A project to assign to a default server.
* @param serverName
* A default server name for the given project.
*/
public static void setDefaultServer(IProject project, String serverName) {
ServersManager manager = getInstance();
Server server = (Server) manager.servers.get(serverName);
setDefaultServer(project, server);
}
/**
* Creates and adds a server.
*
* @param name
* @param baseURL
* @return
* @throws MalformedURLException
*/
public static Server createServer(String name, String baseURL) throws MalformedURLException {
Server server = new Server(name, "localhost", baseURL, ""); //$NON-NLS-1$ //$NON-NLS-2$
server = ServersManager.getServer(server);
addServer(server);
return server;
}
/**
* Save the listed servers into the preferences.
*/
public static void save() {
List<IXMLPreferencesStorable> serversToSave = new ArrayList<IXMLPreferencesStorable>();
for (Server server : getServers()) {
// <none> server only in memory
if (isNoneServer(server))
continue;
serversToSave.add(server);
}
IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(Activator.PLUGIN_ID);
XMLPreferencesWriter.write(preferences, SERVERS_PREFERENCES_KEY, serversToSave);
}
/**
* Finds the local server corresponding to the given project
*
* @param project
*/
public final static Server getLocalServer(IProject project) {
for (Server server : getServers()) {
final String documentRoot = server.getDocumentRoot();
if (documentRoot != null && documentRoot.length() > 0) {
final Path path = new Path(documentRoot);
final IPath fullPath = project.getLocation();
if (fullPath != null && path.isPrefixOf(fullPath)) {
return server;
}
}
}
return null;
}
/*
* (non-Javadoc)
*
* @seejava.beans.PropertyChangeListener#propertyChange(java.beans.
* PropertyChangeEvent)
*/
public void propertyChange(PropertyChangeEvent evt) {
// Listen to any attribute change in the Servers
Server server = (Server) evt.getSource();
String oldValue = (String) evt.getOldValue();
String newValue = (String) evt.getNewValue();
// Replace renamed server
if (evt.getPropertyName().equals(Server.NAME)) {
getInstance().servers.remove(oldValue);
getInstance().servers.put(newValue, server);
}
ServerManagerEvent event = new ServerManagerEvent(ServerManagerEvent.MANAGER_EVENT_MODIFIED, server,
evt.getPropertyName(), oldValue, newValue);
fireEvent(event);
}
@SuppressWarnings("unchecked")
public Object getAdapter(@SuppressWarnings("rawtypes") Class adapter) {
return null;
}
/**
* Fires a ServerManagerEvent to all the registered
* IServersManagerListeners.
*
* @param event
*/
public void fireEvent(ServerManagerEvent event) {
IServersManagerListener[] allListeners = new IServersManagerListener[listeners.size()];
listeners.toArray(allListeners);
if (event.getType() == ServerManagerEvent.MANAGER_EVENT_ADDED) {
fireAddEvent(event, allListeners);
} else if (event.getType() == ServerManagerEvent.MANAGER_EVENT_REMOVED) {
fireRemoveEvent(event, allListeners);
} else if (event.getType() == ServerManagerEvent.MANAGER_EVENT_MODIFIED) {
fireModifiedEvent(event, allListeners);
}
}
// Creates a preferences scope for the given project.
// This scope will be used to search for preferences values.
private static IScopeContext[] createPreferenceScopes(IProject project) {
if (project != null) {
return new IScopeContext[] { new ProjectScope(project), InstanceScope.INSTANCE, DefaultScope.INSTANCE };
}
return new IScopeContext[] { InstanceScope.INSTANCE, DefaultScope.INSTANCE };
}
private void innerSaveDefaultServer(IProject project, Server server) {
IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(Activator.PLUGIN_ID);
// Preferences prefs = Activator.getDefault().getPluginPreferences();
if (project == null && server != null) {
preferences.put(DEFAULT_SERVER_PREFERENCES_KEY, server.getName());
try {
preferences.flush();
} catch (BackingStoreException e) {
Logger.logException(e);
}
} else if (project != null) {
IScopeContext[] scopeContexts = createPreferenceScopes(project);
IEclipsePreferences prefsNode = scopeContexts[0].getNode(NODE_QUALIFIER);
if (server != null) {
prefsNode.put(DEFAULT_SERVER_PREFERENCES_KEY, server.getName());
} else {
prefsNode.remove(DEFAULT_SERVER_PREFERENCES_KEY);
}
try {
prefsNode.flush();
} catch (BackingStoreException e) {
Logger.logException(e);
}
}
}
// Loads the servers from the preferences store.
private void loadServers() {
// Add <none> dummy server first
try {
Server noneServer = new NoneServer();
servers.put(noneServer.getName(), noneServer);
} catch (MalformedURLException e) {
}
// Read all the configurations of the servers from the preferences and
// place them into the
// servers hash (map name to Server instance).
IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(Activator.PLUGIN_ID);
List<Map<String, Object>> serversConfigs = XMLPreferencesReader.read(preferences, SERVERS_PREFERENCES_KEY,
true);
ServersUpgrade upgrader = new ServersUpgrade();
// Then we create the servers from their configurations...
for (Map<String, Object> serverMap : serversConfigs) {
Server server = new Server();
server.restoreFromMap(serverMap);
String serverName = server.getName();
servers.put(serverName, server);
// Register the manager as a Server lister to get notifications
// about attribute changes.
server.addPropertyChangeListener(this);
upgrader.check(serverMap);
}
// Perform upgrade if necessary
upgrader.perform();
// Create default server if there is no any
createDefaultPHPServer();
}
private void createDefaultPHPServer() {
Collection<Server> loaded = servers.values();
if (loaded.size() == 1 && isNoneServer(loaded.iterator().next())) {
Server defaultWebServer = null;
try {
defaultWebServer = new Server(DEFAULT_SERVER_NAME, "localhost", //$NON-NLS-1$
BASE_URL, ""); //$NON-NLS-1$
servers.put(defaultWebServer.getName(), defaultWebServer);
} catch (MalformedURLException e) {
}
// Set as workspace default
defaultServersMap.put(null, defaultWebServer);
innerSaveDefaultServer(null, defaultWebServer);
// Save configurations
List<IXMLPreferencesStorable> serversToSave = new ArrayList<IXMLPreferencesStorable>();
for (Server toSave : servers.values()) {
// <none> server only in memory
if (isNoneServer(toSave))
continue;
serversToSave.add(toSave);
}
IEclipsePreferences preferences = InstanceScope.INSTANCE.getNode(Activator.PLUGIN_ID);
XMLPreferencesWriter.write(preferences, SERVERS_PREFERENCES_KEY, serversToSave);
}
}
private void fireAddEvent(ServerManagerEvent event, IServersManagerListener[] allListeners) {
for (IServersManagerListener element : allListeners) {
element.serverAdded(event);
}
}
private void fireRemoveEvent(ServerManagerEvent event, IServersManagerListener[] allListeners) {
for (IServersManagerListener element : allListeners) {
element.serverRemoved(event);
}
}
private void fireModifiedEvent(ServerManagerEvent event, IServersManagerListener[] allListeners) {
for (IServersManagerListener element : allListeners) {
element.serverModified(event);
}
}
}