/* * JBoss, Home of Professional Open Source. * * See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing. * * See the AUTHORS.txt file distributed with this work for a full listing of individual contributors. */ package org.teiid.designer.runtime; import static org.teiid.designer.runtime.DqpPlugin.PLUGIN_ID; import static org.teiid.designer.runtime.DqpPlugin.Util; import java.io.File; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.xml.transform.TransformerFactoryConfigurationError; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorReference; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.wst.server.core.IServer; import org.teiid.core.designer.util.CoreArgCheck; import org.teiid.datatools.connectivity.ConnectivityUtil; import org.teiid.datatools.connectivity.spi.ISecureStorageProvider; import org.teiid.designer.core.util.KeyInValueHashMap; import org.teiid.designer.core.util.KeyInValueHashMap.KeyFromValueAdapter; import org.teiid.designer.core.workspace.DotProjectUtils; import org.teiid.designer.runtime.IServersProvider.IServersInitialiseListener; import org.teiid.designer.runtime.importer.ImportManager; import org.teiid.designer.runtime.spi.ExecutionConfigurationEvent; import org.teiid.designer.runtime.spi.IExecutionConfigurationListener; import org.teiid.designer.runtime.spi.ITeiidServer; import org.teiid.designer.runtime.spi.ITeiidServerManager; import org.teiid.designer.runtime.spi.ITeiidServerVersionListener; import org.teiid.designer.runtime.version.spi.ITeiidServerVersion; import org.teiid.designer.runtime.version.spi.TeiidServerVersion; /** * The <code>ServerManager</code> class manages the creation, deletion, and editing of servers hosting Teiid Instances. * * @since 8.0 */ public final class TeiidServerManager implements ITeiidServerManager, TeiidServerRegistryConstants { // =========================================================================================================================== // Constants // =========================================================================================================================== /** * Designer UI plugin id */ private static final String DESIGNER_UI_PLUGIN_ID = "org.teiid.designer.ui"; //$NON-NLS-1$ private class TeiidServerKeyValueAdapter implements KeyFromValueAdapter<String, ITeiidServer> { @Override public String getKey(ITeiidServer value) { return value.getId(); } } // =========================================================================================================================== // Fields // =========================================================================================================================== /** * The listeners registered to receive {@link ExecutionConfigurationEvent server registry events}. */ private final CopyOnWriteArrayList<IExecutionConfigurationListener> listeners; /** * The path where the server registry is persisted or <code>null</code> if not persisted. */ private final String stateLocationPath; /** * The server registry. */ private final KeyInValueHashMap<String, ITeiidServer> teiidServers; /** * The default teiid instance. */ private ITeiidServer defaultServer; /** * Listener to the default teiid instance version */ private Set<ITeiidServerVersionListener> teiidServerVersionListeners; /** * Lock used for when accessing the server registry. */ private final ReadWriteLock serverLock = new ReentrantReadWriteLock(); /** * The status of this manager. */ private RuntimeState state = RuntimeState.INVALID; /** * The provider used for accessing the collection of available {@link IServer}s * rather than relying on {@link parentServersProvider} directly, which makes unit testing difficult */ private IServersProvider parentServersProvider; /** * The provider used for storing passwords */ private ISecureStorageProvider secureStorageProvider; public TeiidJdbcPortManager jdbcPortManager = new TeiidJdbcPortManager(); /** * Flag indicating whether other open editors should be closed when the default server * is changed. Will be set to true when the manager is fully started and AFTER the default * server initially set to avoid closing editors from the previous session. */ private boolean closeEditorsOnDefaultServerChange = false; /** * Internal flag to stop signals being sent to listeners. * Should always be called in pairs, * ie. turn off -> do work -> turn on */ private boolean notifyListeners = true; /* Listen for changes to the default teiid instance version preference */ private IPreferenceChangeListener preferenceChangeListener = new IPreferenceChangeListener() { @Override public void preferenceChange(PreferenceChangeEvent event) { if (! ITeiidServerManager.DEFAULT_TEIID_SERVER_VERSION_ID.equals(event.getKey())) return; if (getDefaultServer() != null) { // Default teiid instance exists so this preference is superceded by this server return; } if (getDefaultServerVersion().equals(event.getNewValue())) return; // Server version change has occurred so close all editors and notify server version listeners closeEditors(); if (teiidServerVersionListeners == null) return; for (ITeiidServerVersionListener listener : teiidServerVersionListeners) { listener.versionChanged(getDefaultServerVersion()); } } }; // =========================================================================================================================== // Constructors // =========================================================================================================================== /** * Create default instance */ public TeiidServerManager() { this(DqpPlugin.getInstance().getRuntimePath().toFile().getAbsolutePath(), DqpPlugin.getInstance().getServersProvider(), ConnectivityUtil.getSecureStorageProvider()); } /** * @param stateLocationPath the directory where the {@link ITeiidServer} registry} is persisted (may be <code>null</code> if * persistence is not desired) * @param parentServersProvider * @param secureStorageProvider */ public TeiidServerManager( String stateLocationPath, IServersProvider parentServersProvider, ISecureStorageProvider secureStorageProvider ) { CoreArgCheck.isNotNull(stateLocationPath); CoreArgCheck.isNotNull(parentServersProvider); CoreArgCheck.isNotNull(secureStorageProvider); this.teiidServers = new KeyInValueHashMap<String, ITeiidServer>(new TeiidServerKeyValueAdapter()); this.stateLocationPath = stateLocationPath; this.parentServersProvider = parentServersProvider; this.secureStorageProvider = secureStorageProvider; this.listeners = new CopyOnWriteArrayList<IExecutionConfigurationListener>(); } // =========================================================================================================================== // Methods // =========================================================================================================================== private void checkStarted() { if (! RuntimeState.STARTED.equals(state)) throw new RuntimeException( "Programming error: The TeiidServerManager is being used before it has been initialised"); //$NON-NLS-1$ } /** * Get the implementation of the {@link ISecureStorageProvider} used by * this server manager. * * @return implementation of {@link ISecureStorageProvider} */ @Override public ISecureStorageProvider getSecureStorageProvider() { checkStarted(); return secureStorageProvider; } /** * @return defaultServer */ @Override public ITeiidServer getDefaultServer() { checkStarted(); return defaultServer; } /** * @param id the id of the server being requested (never <code>null</code> ) * @return the requested server or <code>null</code> if not found in the registry */ @Override public ITeiidServer getServer( String id ) { checkStarted(); CoreArgCheck.isNotNull(id, "id"); //$NON-NLS-1$ return teiidServers.get(id); } /** * @param parentServer the parent server of the requested Teiid Instance * @return the requested server or <code>null</code> if not found in the registry */ @Override public ITeiidServer getServer( IServer parentServer) { checkStarted(); CoreArgCheck.isNotNull(parentServer, "parentServer"); //$NON-NLS-1$ for (ITeiidServer teiidServer : getServers()) { if (parentServer.equals(teiidServer.getParent())) { return teiidServer; } } return null; } /** * @return an unmodifiable collection of registered servers (never <code>null</code>) */ @Override public Collection<ITeiidServer> getServers() { checkStarted(); try { this.serverLock.readLock().lock(); return Collections.unmodifiableCollection(this.teiidServers.values()); } finally { this.serverLock.readLock().unlock(); } } private ITeiidServerVersion getDefaultServerVersionInternal() { if (this.defaultServer == null) { // Preference is stored in the ui plugin which we do not have a dependency on so have to // get at the preference in this way. Not great but alternative is to try and save the property // in this plugin's properties and not really worth it. IEclipsePreferences preferences = DqpPlugin.getInstance().getPreferences(DESIGNER_UI_PLUGIN_ID); ITeiidServerVersion defaultVersion = TeiidServerVersion.deriveUltimateDefaultServerVersion(); String versionString = preferences.get(DEFAULT_TEIID_SERVER_VERSION_ID, defaultVersion.toString()); return new TeiidServerVersion(versionString); } return defaultServer.getServerVersion(); } /** * Get the targeted Teiid Instance version * * @return Teiid Instance version */ @Override public ITeiidServerVersion getDefaultServerVersion() { checkStarted(); return getDefaultServerVersionInternal(); } @Override public RuntimeState getState() { return this.state; } @Override public boolean isStarted() { return RuntimeState.STARTED.equals(this.state); } /** * @return the name of the state file that the server registry is persisted to or <code>null</code> */ public final String getStateFileName() { String name = this.stateLocationPath; if (this.stateLocationPath != null) { name += File.separatorChar + REGISTRY_FILE; } return name; } /** * Registers the specified <code>Server</code>. * * @param teiidServer the server being added * @param notifyListeners indicates if registry listeners should be notified * @return a status indicating if the server was added to the registry */ private IStatus addServerInternal( ITeiidServer teiidServer, boolean notifyListeners ) { CoreArgCheck.isNotNull(teiidServer, "server"); //$NON-NLS-1$ boolean added = false; ITeiidServer defaultServer = null; try { this.serverLock.writeLock().lock(); if (!isRegisteredInternal(teiidServer)) { if (teiidServers.isEmpty()) { defaultServer = teiidServer; } added = this.teiidServers.add(teiidServer); } } finally { this.serverLock.writeLock().unlock(); } if (added) { if (notifyListeners) { notifyListeners(ExecutionConfigurationEvent.createAddServerEvent(teiidServer)); } if( defaultServer != null ) { setDefaultServerInternal(defaultServer); } return Status.OK_STATUS; } // server already exists return new Status(IStatus.ERROR, PLUGIN_ID, Util.getString("serverExistsMsg", teiidServer)); //$NON-NLS-1$ } /** * Registers the specified <code>PersistedServer</code>. * * @param teiidServer the server being added (never <code>null</code>) * @return a status indicating if the server was added to the registry */ @Override public IStatus addServer( ITeiidServer teiidServer ) { checkStarted(); return addServerInternal(teiidServer, true); } /** * @param teiidServer the server being removed * @param notifyListeners indicates if registry listeners should be notified * @return a status indicating if the specified server was removed from the registry */ private IStatus removeServerInternal( ITeiidServer teiidServer, boolean notifyListeners ) { ITeiidServer removed = null; try { this.serverLock.writeLock().lock(); removed = this.teiidServers.remove(teiidServer); // Check if removed server is default, then set to first server if (teiidServer.equals(getDefaultServer())) { // If no servers left, set defaultServer to null if (this.teiidServers.isEmpty()) setDefaultServerInternal(null); else setDefaultServerInternal(this.teiidServers.values().iterator().next()); } } finally { this.serverLock.writeLock().unlock(); } if (removed != null) { if (notifyListeners) { notifyListeners(ExecutionConfigurationEvent.createRemoveServerEvent(teiidServer)); } return Status.OK_STATUS; } // server could not be removed return new Status(IStatus.ERROR, PLUGIN_ID, Util.getString("serverManagerRegistryRemoveUnexpectedError", teiidServer)); //$NON-NLS-1$ } /** * @param teiidServer the server being removed (never <code>null</code>) * @return a status indicating if the specified segetUrlrver was removed from the registry (never <code>null</code>) */ @Override public IStatus removeServer( ITeiidServer teiidServer ) { checkStarted(); CoreArgCheck.isNotNull(teiidServer, "server"); //$NON-NLS-1$ return removeServerInternal(teiidServer, true); } /** * Is this server the default * * @param teiidServer * * @return true if this server is the default, false otherwise. */ @Override public boolean isDefaultServer( ITeiidServer teiidServer ) { checkStarted(); CoreArgCheck.isNotNull(teiidServer, "server"); //$NON-NLS-1$ if (this.defaultServer == null) { return false; } return this.defaultServer.equals(teiidServer); } private boolean isRegisteredInternal(ITeiidServer teiidServer) { try { this.serverLock.readLock().lock(); return this.teiidServers.containsValue(teiidServer); } finally { this.serverLock.readLock().unlock(); } } /** * @param teiidServer the server being tested (never <code>null</code>) * @return <code>true</code> if the server has been registered */ @Override public boolean isRegistered( ITeiidServer teiidServer ) { checkStarted(); CoreArgCheck.isNotNull(teiidServer, "server"); //$NON-NLS-1$ return isRegisteredInternal(teiidServer); } /** * {@inheritDoc} * * @see org.teiid.designer.runtime.spi.EventManager#notifyListeners(org.teiid.designer.runtime.spi.ExecutionConfigurationEvent) */ @Override public void permitListeners(boolean enable) { this.notifyListeners = enable; } /** * {@inheritDoc} * * @see org.teiid.designer.runtime.spi.EventManager#notifyListeners(org.teiid.designer.runtime.spi.ExecutionConfigurationEvent) */ @Override public void notifyListeners( ExecutionConfigurationEvent event ) { if (!notifyListeners) return; if (RuntimeState.SHUTTING_DOWN.equals(getState()) || RuntimeState.SHUTDOWN.equals(getState())) { // Since we are shutting down then everything is being saved and no reason to notify UI return; } for (IExecutionConfigurationListener l : this.listeners) { try { l.configurationChanged(event); } catch (Exception e) { removeListener(l); Util.log(IStatus.WARNING, e, Util.getString("unexpectedErrorInExecutionConfigurationListener", l)); //$NON-NLS-1$ } } } /** * Initialise those managers and listeners under the control of the server manager * that may well rely on it. */ private void initialiseManagers() { addListener(ImportManager.getInstance()); IEclipsePreferences preferences = DqpPlugin.getInstance().getPreferences(DESIGNER_UI_PLUGIN_ID); preferences.addPreferenceChangeListener(preferenceChangeListener); } private void restoreStateInternal() { // Initialize teiid parent server state listener parentServersProvider.addServerStateListener(TeiidParentServerListener.getInstance()); parentServersProvider.addServerLifecycleListener(TeiidParentServerListener.getInstance()); ITeiidServer defaultServer = null; try { this.notifyListeners = false; if (this.stateLocationPath == null || ! stateFileExists()) { // Started will be called from the finally clause. return; } // NOTE ================================================= // FOR DEBUGGING PURPOSES.. set boolean to TRUE below to print out the server attributes and properties // to the console as they are loaded TeiidServerRegistryReader reader = new TeiidServerRegistryReader( this, parentServersProvider, this.secureStorageProvider, false); Collection<ITeiidServer> loadedServers = reader.restoreServers(); for( ITeiidServer server: loadedServers ) { addServerInternal(server, true); } ITeiidServer loadedDefaultServer = reader.getDefaultServer(); if( loadedDefaultServer != null ) { defaultServer = loadedDefaultServer; } else if( !loadedServers.isEmpty() ) { defaultServer = loadedServers.iterator().next(); } } catch (Exception e) { Util.log(e); } finally { this.notifyListeners = true; this.state = RuntimeState.STARTED; initialiseManagers(); // Set the default teiid instance. Doing this here will allow the managers to detect the change // and initialise accordingly setDefaultServerInternal(defaultServer); // Set the default server for the first time so set the close editors flag to true, ensuring that // editors will be subsequently be closed when the default server version is changed. closeEditorsOnDefaultServerChange = true; } } @Override public void restoreState() { this.state = RuntimeState.RESTORING; if (parentServersProvider.isInitialised()) { restoreStateInternal(); return; } // Not yet initialised so install a listener to await the initialisation IServersInitialiseListener listener = new IServersInitialiseListener() { @Override public void serversInitialised() { restoreStateInternal(); } }; parentServersProvider.addServerInitialisedListener(listener); } private void setDefaultServerInternal(ITeiidServer teiidServer) { if (this.defaultServer == null && teiidServer == null) { // Both are null so no point in continuing return; } ITeiidServer oldDefaultServer = this.defaultServer; this.defaultServer = teiidServer; if (teiidServerVersionListeners != null) { // Notify the server version listeners that the server has changed for (ITeiidServerVersionListener listener : teiidServerVersionListeners) { listener.serverChanged(defaultServer); } } /* * Compare the versions of the server. * * Only if the server version has changed should the model editors be closed * and server version listeners notified. */ ITeiidServerVersion oldServerVersion = oldDefaultServer != null ? oldDefaultServer.getServerVersion() : null; if (teiidServerVersionListeners != null && ! getDefaultServerVersionInternal().equals(oldServerVersion)) { // Server version change has occurred so close all editors and notify server version listeners closeEditors(); for (ITeiidServerVersionListener listener : teiidServerVersionListeners) { listener.versionChanged(getDefaultServerVersionInternal()); } } notifyListeners(ExecutionConfigurationEvent.createSetDefaultServerEvent(oldDefaultServer, this.defaultServer)); } /** * @param teiidServer Sets defaultServer to the specified value. May be null. */ @Override public void setDefaultServer( ITeiidServer teiidServer ) { checkStarted(); setDefaultServerInternal(teiidServer); } @Override public void setJdbcPort(ITeiidServer server, int port, boolean isOverride) { jdbcPortManager.setPort(server, port, isOverride); } @Override public String getJdbcPort(ITeiidServer server, boolean isOverride) { return jdbcPortManager.getPort(server, isOverride); } public final TeiidJdbcPortManager getJdbcPortManager() { return jdbcPortManager; } /** * Close editors associated with modelling projects */ private void closeEditors() { if (RuntimeState.RESTORING == state || ! closeEditorsOnDefaultServerChange) { // Avoid closing editors on startup since the default teiid instance is simply being assigned return; } Display display = PlatformUI.getWorkbench().getDisplay(); if (display == null) return; // Ensure that this is performed on the UI thread display.asyncExec(new Runnable() { @Override public void run() { for (IWorkbenchWindow window : PlatformUI.getWorkbench().getWorkbenchWindows()) { for (IWorkbenchPage page : window.getPages()) { IEditorReference[] editorReferences = page.getEditorReferences(); for (IEditorReference editorRef : editorReferences) { IEditorInput input; try { input = editorRef.getEditorInput(); IResource resource = (IResource)input.getAdapter(IResource.class); // Only close those editors associated with modelling projects // rather than blindly closing all editors if (resource != null && DotProjectUtils.isModelerProject(resource.getProject())) { page.closeEditor(editorRef.getEditor(true), true); } } catch (PartInitException ex) { DqpPlugin.Util.log(ex); } } } } } }); } @Override public void shutdown( IProgressMonitor monitor ) throws Exception { // return if already being shutdown if ((this.state == RuntimeState.SHUTTING_DOWN) || (this.state == RuntimeState.SHUTDOWN)) return; try { this.state = RuntimeState.SHUTTING_DOWN; if (monitor != null) monitor.subTask(Util.getString("serverManagerSavingServerRegistryTask")); //$NON-NLS-1$ IEclipsePreferences preferences = DqpPlugin.getInstance().getPreferences(DESIGNER_UI_PLUGIN_ID); if (preferences != null) preferences.removePreferenceChangeListener(preferenceChangeListener); saveState(); } finally { this.state = RuntimeState.SHUTDOWN; } } private void saveState() throws TransformerFactoryConfigurationError { if (this.stateLocationPath == null) return; // no persistence if (teiidServers.isEmpty()) { if (stateFileExists()) { // delete current registry file since all servers have been deleted try { new File(getStateFileName()).delete(); } catch (Exception e) { IStatus status = new Status(IStatus.ERROR, PLUGIN_ID, Util.getString("errorDeletingServerRegistryFile", getStateFileName())); //$NON-NLS-1$ Util.log(status); } } return; } // NOTE ================================================= // FOR DEBUGGING PURPOSES.. set boolean to TRUE below to print out the server attributes and properties // to the console as they are written out TeiidServerRegistryWriter writer = new TeiidServerRegistryWriter( this, defaultServer, false); writer.storeServers(teiidServers.values()); } /** * @return <code>true</code> if the state file already exists */ private boolean stateFileExists() { return new File(getStateFileName()).exists(); } /** * Updates the server registry with a new version of a server. * * @param replacedServer the version of the server being replaced (never <code>null</code>) * @param updatedServer the new version of the server being put in the server registry (never <code>null</code>) * @return a status indicating if the server was updated in the registry (never <code>null</code>) */ public IStatus updateServer( ITeiidServer replacedServer, ITeiidServer updatedServer ) { checkStarted(); CoreArgCheck.isNotNull(replacedServer, "previousServerVersion"); //$NON-NLS-1$ CoreArgCheck.isNotNull(updatedServer, "newServerVersion"); //$NON-NLS-1$ IStatus status = null; try { this.serverLock.writeLock().lock(); status = removeServerInternal(replacedServer, false); if (status.isOK()) { status = addServerInternal(updatedServer, false); if (status.isOK()) { // all good so notify listeners notifyListeners(ExecutionConfigurationEvent.createUpdateServerEvent(replacedServer, updatedServer)); return status; } // unexpected problem adding new version of server to registry so add old one back IStatus undoRemoveServerStatus = addServerInternal(replacedServer, false); if (undoRemoveServerStatus.getSeverity() == IStatus.ERROR) { Util.log(undoRemoveServerStatus); } return new Status(IStatus.ERROR, PLUGIN_ID, Util.getString("serverManagerRegistryUpdateAddError", status.getMessage())); //$NON-NLS-1$ } } finally { this.serverLock.writeLock().unlock(); } // unexpected problem removing server from registry return new Status(IStatus.ERROR, PLUGIN_ID, Util.getString("serverManagerRegistryUpdateRemoveError", status.getMessage())); //$NON-NLS-1$ } /** * {@inheritDoc} * * @see org.teiid.designer.runtime.spi.EventManager#addListener(org.teiid.designer.runtime.spi.IExecutionConfigurationListener) */ @Override public boolean addListener( IExecutionConfigurationListener listener ) { CoreArgCheck.isNotNull(listener, "listener"); //$NON-NLS-1$ return this.listeners.addIfAbsent(listener); } /** * {@inheritDoc} * * @see org.teiid.designer.runtime.spi.EventManager#removeListener(org.teiid.designer.runtime.spi.IExecutionConfigurationListener) */ @Override public boolean removeListener( IExecutionConfigurationListener listener ) { CoreArgCheck.isNotNull(listener, "listener"); //$NON-NLS-1$ return this.listeners.remove(listener); } @Override public void addTeiidServerVersionListener(ITeiidServerVersionListener listener) { if (teiidServerVersionListeners == null) teiidServerVersionListeners = new HashSet<ITeiidServerVersionListener>(); teiidServerVersionListeners.add(listener); } @Override public void removeTeiidServerVersionListener(ITeiidServerVersionListener listener) { if (teiidServerVersionListeners == null) return; teiidServerVersionListeners.remove(listener); } }