/* * Copyright 2000-2020 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jetbrains.teamcity; import com.intellij.openapi.diagnostic.Logger; import jetbrains.buildServer.Ide; import jetbrains.buildServer.TeamCityState; import jetbrains.buildServer.serverSide.BasePropertiesModel; import jetbrains.buildServer.serverSide.TeamCityProperties; import jetbrains.buildServer.util.StringUtil; import jetbrains.teamcity.Constants.Preferences; import jetbrains.teamcity.EclipseIDE.TCCommunicationException; import jetbrains.teamcity.core.*; import jetbrains.teamcity.core.compatibility.ServiceFactory; import jetbrains.teamcity.core.compatibility.net.IProxyService; import jetbrains.teamcity.core.compatibility.net.NullProxyService; import jetbrains.teamcity.core.jobs.LoginJob; import jetbrains.teamcity.core.remote.ICommitMessageProcessor; import jetbrains.teamcity.core.remote.IRemoteRunProvider; import jetbrains.teamcity.core.shelve.ShelveManager; import jetbrains.teamcity.ui.NotificationMessagesToTrayEmitter; import jetbrains.teamcity.vcs.TCShelveHandler; import jetbrains.teamcity.vcs.TCWorkspaceRemoteRunProvider; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.equinox.security.storage.ISecurePreferences; import org.eclipse.equinox.security.storage.SecurePreferencesFactory; import org.eclipse.equinox.security.storage.StorageException; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.util.SafeRunnable; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.plugin.AbstractUIPlugin; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.osgi.framework.BundleContext; import org.osgi.framework.BundleEvent; import org.osgi.framework.BundleListener; import java.io.IOException; import java.text.MessageFormat; import java.util.*; /** * The activator class controls the plug-in life cycle */ public class Activator extends AbstractUIPlugin { public static final NullProxyService NULL_PROXY_SERVICE = new NullProxyService(); // the plugin's feature ID public static final String FEATURE_ID = "jetbrains.teamcity.feature"; //$NON-NLS-1$ // The plug-in ID public static final String PLUGIN_ID = "jetbrains.teamcity.core"; //$NON-NLS-1$ public static final String PERSPECTIVE_ID = "jetbrains.teamcity.ui.TeamCityPerspective"; //$NON-NLS-1$ private static final String URL_PROPERTY = "url"; //$NON-NLS-1$ private static final String USERNAME_PROPERTY = "username"; //$NON-NLS-1$ private static final String PASSWORD_PROPERTY = "password"; //$NON-NLS-1$ private static final String REALM = Constants.Authorization.REALM; // private Subscriber[] mySubscribers; // The shared instance private static Activator plugin; private static final ArrayList ourPropertyChangeListeners = new ArrayList(5); private Map myFormToolkits; private TeamCitySnapshot4Eclipse myTeamCitySnapshot; private EclipseIDE myTeamCityIDE; private IProxyService myProxyService; private ArrayList myProviders; private TCShelveHandler shelveHandler; private NotificationDispatcher myTeamCityNotificationDispatcher; private TaskService myTaskService; private BundleListener myBundleListener; private LoginJob myAutoLoginJob; /** * The constructor */ public Activator() { plugin = this; } /* * (non-Javadoc) * * @see * org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext * ) */ @Override public void start(BundleContext context) throws Exception { super.start(context); // setupVcs(); // Initialize new TeamCityProperties() {{ setModel(new BasePropertiesModel() { }); }}; myFormToolkits = new HashMap(); final EclipseIDESettings settings = loadLoginSettings(); myTeamCityIDE = new EclipseIDE(); try { createSnapshot(myTeamCityIDE, settings != null ? settings : EclipseIDESettings.DEFAULT); if (settings != null) { myAutoLoginJob = new LoginJob(getSnapshot(), null, settings, null); } } catch (TCCommunicationException e) { getLog().log(e.getStatus()); } myBundleListener = new BundleListener() { public void bundleChanged(BundleEvent event) { final String id = event.getBundle().getSymbolicName(); if (!PLUGIN_ID.equals(id)) { return; } if (event.getType() == BundleEvent.STARTED) { if (myAutoLoginJob != null) { myAutoLoginJob.schedule(); } } } }; context.addBundleListener(myBundleListener); setupListeners(); setupRemainders(); NotificationMessagesToTrayEmitter.register(); } private void setupListeners() { //shelve shelveHandler = new TCShelveHandler(); ShelveManager.getInstance().addSupport(shelveHandler); //TC notifications myTeamCityNotificationDispatcher = new NotificationDispatcher(); } private void disposeListeners() { //TC notifications NotificationMessagesToTrayEmitter.unregister(); if (myTeamCityNotificationDispatcher != null) { myTeamCityNotificationDispatcher.dispose(); } ShelveManager.getInstance().removeSupport(shelveHandler); ProjectStateDispatcher.getInstance().dispose(); } /** * refresh TC each midnight */ private void setupRemainders() { getTaskService().schedule(TaskService.getNextMidnight(), TaskService.EVERY_DAY, new Runnable() { public void run() { getDefault().getSnapshot().scheduleUpdate(); } }); } public String getLastLoggedServer() { final IPreferenceStore store = Activator.getDefault().getPreferenceStore(); final String url = store.getString(Constants.Preferences.LAST_LOGGED_SERVER_URL); if (url != null && url.trim().length() > 0) { return url; } return null; } public void setLastLoggedServer(final EclipseIDESettings server) { if (server != null) { final IPreferenceStore store = Activator.getDefault().getPreferenceStore(); store.setValue(Constants.Preferences.LAST_LOGGED_SERVER_URL, server.getServerUrl()); store.setValue(Constants.Preferences.LAST_LOGGED_SERVER_USERNAME, server.getLogin()); } } public EclipseIDE getIDE() { return myTeamCityIDE; } public TeamCitySnapshot4Eclipse getSnapshot() { return myTeamCitySnapshot; } private TeamCitySnapshot4Eclipse createSnapshot(final Ide ide, final EclipseIDESettings settings) throws TCCommunicationException { // releaseSnapshot(); final TeamCitySnapshot4Eclipse newSnapshot = new TeamCitySnapshot4Eclipse(ide, settings/*, new TeamCityProcessExecutor()*/); newSnapshot.init(); myTeamCitySnapshot = newSnapshot; getIDE().setSnapshot(myTeamCitySnapshot); return myTeamCitySnapshot; } private void releaseSnapshot() { if (myTeamCitySnapshot != null) { // release if (myTeamCitySnapshot.getState() == TeamCityState.LOGGED_IN || myTeamCitySnapshot.getState() == TeamCityState.LOGGING_IN) { myTeamCitySnapshot.logout(); } myTeamCitySnapshot.dispose(); } } @Override public void stop(BundleContext context) throws Exception { //detach listeners disposeListeners(); //release toolkits if (myFormToolkits != null) { for (Display display : myFormToolkits.keySet()) { myFormToolkits.get(display).dispose(); } myFormToolkits = null; } //dispose others myTeamCityIDE.dispose(); context.removeBundleListener(myBundleListener); releaseSnapshot(); super.stop(context); } /** * Returns the shared instance * * @return the shared instance */ public static Activator getDefault() { return plugin; } public void saveLoginSettings(@Nullable final EclipseIDESettings settings) { try { final ISecurePreferences root = SecurePreferencesFactory.getDefault(); if (root == null) { Debug.getInstance().log(Debug.DEBUG_PLATFORM, "Application was unable to create secure preferences using default Equinox Secure Preferences location"); return; } final ISecurePreferences node = root.node(REALM); if (settings != null) { node.put(URL_PROPERTY, settings.getServerUrl(), false); node.put(USERNAME_PROPERTY, settings.getLogin(), false); node.put(PASSWORD_PROPERTY, new String(settings.getPassword()), true); } else { node.remove(URL_PROPERTY); node.remove(USERNAME_PROPERTY); node.remove(PASSWORD_PROPERTY); } node.flush(); } catch (StorageException e) { Debug.getInstance().log(Debug.DEBUG_PLATFORM, e); } catch (IllegalStateException e) { Debug.getInstance().log(Debug.DEBUG_PLATFORM, e); } catch (IOException e) { Debug.getInstance().log(Debug.DEBUG_PLATFORM, e); } } @Nullable public EclipseIDESettings loadLoginSettings() { try { final ISecurePreferences root = SecurePreferencesFactory.getDefault(); if (root == null) { Debug.getInstance().log(Debug.DEBUG_PLATFORM, "Application was unable to create secure preferences using default Equinox Secure Preferences location"); return null; } final ISecurePreferences node = root.node(REALM); final String url = node.get(URL_PROPERTY, null); final String username = node.get(USERNAME_PROPERTY, null); final String password = node.get(PASSWORD_PROPERTY, null); if (url != null) { return new EclipseIDESettings(username, password, url, getConnectionParams(), getProxyService()); } } catch (StorageException e) { Debug.getInstance().log(Debug.DEBUG_PLATFORM, e); } catch (IllegalStateException e) { Debug.getInstance().log(Debug.DEBUG_PLATFORM, e); } return null; } public FormToolkit getFormToolkit(Display display) { if (!myFormToolkits.containsKey(display)) { FormToolkit toolkit = new FormToolkit(display); toolkit.getColors().initializeSectionToolBarColors(); myFormToolkits.put(display, toolkit); } return myFormToolkits.get(display); } private Collection getProviders() { if (myProviders == null) { final ArrayList buffer = new ArrayList(); // load extension point final Collection extensions = Util.loadExtension(IRemoteRunProvider.EXTENSION_NAME); final Logger log = Debug.Category.DEBUG_VCS.getLogger(Activator.class); for (IConfigurationElement ext : extensions) { final String name = ext.getAttribute(IRemoteRunProvider.EXTENSION_ATTRIBUTE_NAME); try { final Object instance = ext.createExecutableExtension(IRemoteRunProvider.EXTENSION_ATTRIBUTE_CLASS_NAME); if (instance instanceof IRemoteRunProvider) { buffer.add((IRemoteRunProvider) instance); final boolean active = ((IRemoteRunProvider) instance).isActive(); if (active) { log.debug(String.format("%s support found, Active", name)); //$NON-NLS-1$ } else { log.debug(String.format("%s support found, but it's Inactive", name)); //$NON-NLS-1$ } } else { log.debug(String.format("%s is not IRemoteRunProvider. %s support disabled", instance != null ? instance.getClass().getName() : null, name)); //$NON-NLS-1$ } } catch (Throwable e) { e = ExceptionUtil.getCoreExceptionCause(e); log.debug(String.format("%s support disabled due to exception: %s", name, e.getLocalizedMessage())); //$NON-NLS-1$ } } myProviders = buffer; } return myProviders; } private final Lazy> myCommitMessageProcessors = new Lazy>() { @Override protected Map create() { final TreeMap result = new TreeMap(); // load extension point final List extensions = new ArrayList(Util.loadExtension(ICommitMessageProcessor.EXTENSION_NAME)); final Logger log = Debug.Category.DEBUG_VCS.getLogger(Activator.class); Collections.sort(extensions, new Comparator() { public int compare(IConfigurationElement o1, IConfigurationElement o2) { final String o1name = o1.getAttribute(ICommitMessageProcessor.EXTENSION_ATTRIBUTE_NAME); final String o2name = o2.getAttribute(ICommitMessageProcessor.EXTENSION_ATTRIBUTE_NAME); return StringUtil.compare(o1name, o2name); } }); for (IConfigurationElement ext : extensions) { final String name = ext.getAttribute(ICommitMessageProcessor.EXTENSION_ATTRIBUTE_NAME); try { final Object instance = ext.createExecutableExtension(ICommitMessageProcessor.EXTENSION_ATTRIBUTE_CLASS_NAME); if (instance instanceof ICommitMessageProcessor) { result.put(name, (ICommitMessageProcessor) instance); final boolean active = ((ICommitMessageProcessor) instance).isActive(); if (active) { log.debug(String.format("%s commit message processor found, Active", name)); //$NON-NLS-1$ } else { log.debug(String.format("%s commit message processor found, but it's Inactive", name)); //$NON-NLS-1$ } } else { log.debug(String.format("%s with name %s is not a ICommitMessageProcessor.", instance != null ? instance.getClass().getName() : null, name)); //$NON-NLS-1$ } } catch (Throwable e) { e = ExceptionUtil.getCoreExceptionCause(e); log.debug(String.format("%s commit message processor disabled due to exception: %s", name, e.getLocalizedMessage())); //$NON-NLS-1$ } } return result; } }; public Collection getCommitMessageProcessorNames() { return myCommitMessageProcessors.get().keySet(); } @Nullable public ICommitMessageProcessor getSelectedCommitMessageProcessor() { final String name = getPreferenceStore().getString(Preferences.REMOTE_RUN_COMMIT_MESSAGE_PROCESSOR); if (name == null || ICommitMessageProcessor.NONE.equals(name)) { return null; } return myCommitMessageProcessors.get().get(name); } /** * Returns an image descriptor for the image file at the given plug-in * relative path * * @param path the path * @return the image descriptor */ static ImageDescriptor getImageDescriptor(@NotNull final String path) { return imageDescriptorFromPlugin(PLUGIN_ID, path); } public static void addPropertyChangeListener(IPropertyChangeListener listener) { ourPropertyChangeListeners.add(listener); } public static void removePropertyChangeListener(IPropertyChangeListener listener) { ourPropertyChangeListeners.remove(listener); } public static void broadcastPropertyChange(PropertyChangeEvent event) { for (IPropertyChangeListener listener : ourPropertyChangeListeners) { listener.propertyChange(event); } } public NotificationDispatcher getNotificationDispatcher() { return myTeamCityNotificationDispatcher; } public IProxyService getProxyService() { if (myProxyService == null) { myProxyService = ServiceFactory.getService(IProxyService.class); } if (myProxyService == null) { return NULL_PROXY_SERVICE; } return myProxyService; } public TaskService getTaskService() { if (myTaskService == null) { myTaskService = new TaskService(); } return myTaskService; } public static class TaskService { public static final long EVERY_DAY = 24 * 60 * 60 * 1000; private Timer myTimer; private final HashMap myScheduledTasks = new HashMap(); private synchronized Timer getTimer() { if (myTimer == null) { myTimer = new Timer(Messages.getString("Activator.teamcity.remainder.thread.name"), true); //$NON-NLS-1$ } return myTimer; } public static Date getNextMidnight() { final Calendar instance = Calendar.getInstance(); instance.set(Calendar.HOUR_OF_DAY, 0); instance.set(Calendar.MINUTE, 0); instance.set(Calendar.SECOND, 0); instance.set(Calendar.MILLISECOND, 1); instance.roll(Calendar.DATE, 1); return instance.getTime(); } public void schedule(final @NotNull Date firstTime, final long periodMs, final @NotNull Runnable task) { cancel(task); final String taskKey = getTaskKey(task); final TimerTask timerTask = new TimerTask() { @Override public void run() { try { task.run(); } catch (Throwable t) { Debug.getInstance().log(Debug.DEBUG_PLATFORM, String.format("Task execution failed: %s", t.getMessage())); } } }; myScheduledTasks.put(taskKey, timerTask); if (firstTime.before(new Date())) { getTimer().schedule(timerTask, new Date(), periodMs); } else { getTimer().schedule(timerTask, firstTime, periodMs); } } private String getTaskKey(final Runnable task) { return String.format("TimerTask-%d", task.hashCode()); } public boolean cancel(final @NotNull Runnable task) { final String taskId = getTaskKey(task); if (myScheduledTasks.containsKey(taskId)) { myScheduledTasks.get(taskId).cancel(); myScheduledTasks.remove(taskId); return true; } return false; } } private TCWorkspaceRemoteRunProvider myProvider; public synchronized TCWorkspaceRemoteRunProvider getProvider() { if (myProvider == null || myProvider.isDisposed()) { myProvider = new TCWorkspaceRemoteRunProvider(getProviders()); } myProvider.addRef(); return myProvider; } private ConnectionPreferences myConnectionPreferences; public synchronized IConnectionParams getConnectionParams() { if (myConnectionPreferences == null) { int interval; boolean restore; // load exists try { interval = getPreferenceStore().getInt(Preferences.UPDATE_INTERVAL); restore = getPreferenceStore().getBoolean(Preferences.RESTORE_CONNECTION); } catch (Throwable t) { interval = EclipseIDESettings.DEFAULT_REFRESH_INTERVAL; restore = EclipseIDESettings.RESTORE_CONNECTION; } // create myConnectionPreferences = new ConnectionPreferences(interval * 1000, restore); // listen changes getPreferenceStore().addPropertyChangeListener(new IPropertyChangeListener() { public void propertyChange(final PropertyChangeEvent event) { SafeRunner.run(new SafeRunnable() { public void run() throws Exception { if (Preferences.UPDATE_INTERVAL.equals(event.getProperty())) { myConnectionPreferences.setUpdateInterval(((Integer) event.getNewValue()) * 1000/* seconds in UI */); } if (Preferences.RESTORE_CONNECTION.equals(event.getProperty())) { myConnectionPreferences.setRestoreConnection((Boolean) event.getNewValue()); } } }); } }); } return myConnectionPreferences; } public static interface IConnectionParams { public abstract int getUpdateInterval(); public abstract boolean isRestoreConnection(); } class ConnectionPreferences implements IConnectionParams { public ConnectionPreferences(final int updateInterval, final boolean isRestoreConnection) { setUpdateInterval(updateInterval); setRestoreConnection(isRestoreConnection); } private int myUpdateInterval; private boolean isRestoreConnection; public synchronized int getUpdateInterval() { return myUpdateInterval; } public synchronized boolean isRestoreConnection() { return isRestoreConnection; } synchronized void setUpdateInterval(int updateInterval) { myUpdateInterval = updateInterval; Debug.getInstance().log(Debug.DEBUG_TEAMCITY, MessageFormat.format("Update interval set to {0} ms", updateInterval)); //$NON-NLS-1$ } synchronized void setRestoreConnection(boolean isRestoreConnection) { this.isRestoreConnection = isRestoreConnection; if (isRestoreConnection) { Debug.getInstance().log(Debug.DEBUG_TEAMCITY, "Reconnection enabled"); //$NON-NLS-1$ } else { Debug.getInstance().log(Debug.DEBUG_TEAMCITY, "Reconnection disabled"); //$NON-NLS-1$ } } } }