/******************************************************************************* * Copyright (c) 2013, 2014 Pivotal Software, Inc. * * All rights reserved. This program and the accompanying materials * are made available under the terms of 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. * * Contributors: * Pivotal Software, Inc. - initial API and implementation ********************************************************************************/ package org.cloudfoundry.ide.eclipse.server.ui.internal; import java.util.ArrayList; import java.util.List; import org.cloudfoundry.client.lib.domain.CloudDomain; import org.cloudfoundry.ide.eclipse.server.core.internal.ApplicationUrlLookupService; import org.cloudfoundry.ide.eclipse.server.core.internal.CloudApplicationURL; import org.cloudfoundry.ide.eclipse.server.ui.internal.wizards.CloudUIEvent; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; /** * * Allows users to edit or add an application URL based on a list of existing * URL Cloud domains. * <p/> * Application URL is defined by subdomain and domain segments. The UP part may * keep track of a raw, unparsed URL , whether invalid or not, as the user has * option to edit the full URL manually without necessarily specifying either a * subdomain or domain. * <p/> * Any changes to the raw URL will be parsed into subdomain and domain segments * if possible, based on a list of available domains for the account. If * successfully parsed, any subdomain and domain controls will be updated as * well. If not successfully parsed, at the very minimum any full URL controls * will be kept up to date with the invalid URL. * <p/> * Any registered listeners are notified when there are changes to the URL, or * any errors have occurred during validation or parsing of the URL. */ public class CloudApplicationUrlPart extends UIPart { protected final ApplicationUrlLookupService lookupService; /** * The current URL being edited, in raw form. Note that since URL value in * the UI needs to always be up to date but the URL itself may be invalid * and even not par */ private String currentUrl; private Control validationSource; private Text subDomainText; private Text fullURLText; private Combo domainCombo; public CloudApplicationUrlPart(ApplicationUrlLookupService lookupService) { this.lookupService = lookupService; } /** * Note that the part will adapt the given parent into a 2 column parent * that does not grab vertically. This is to allow other two column controls * in the same parent to have the same column widths as the controls for the * URL part. Callers are responsible for creating an appropriate parent that * only contains the URL part, if they do not wish for other controls * outside this part to be affected. */ public Composite createPart(Composite parent) { Composite subDomainComp = parent; GridLayoutFactory.fillDefaults().numColumns(2).applyTo(subDomainComp); GridDataFactory.fillDefaults().grab(true, false).applyTo(subDomainComp); Label label = new Label(subDomainComp, SWT.NONE); label.setText(Messages.CloudApplicationUrlPart_TEXT_SUBDOMAIN_LABEL); GridDataFactory.fillDefaults().grab(false, false).align(SWT.FILL, SWT.CENTER).applyTo(label); subDomainText = new Text(subDomainComp, SWT.BORDER); GridDataFactory.fillDefaults().grab(true, false).applyTo(subDomainText); subDomainText.addModifyListener(new ModifyListener() { public void modifyText(ModifyEvent arg0) { resolveUrlFromSubdomain(subDomainText); } }); label = new Label(subDomainComp, SWT.NONE); label.setText(Messages.COMMONTXT_DOMAIN); GridDataFactory.fillDefaults().grab(false, false).align(SWT.FILL, SWT.CENTER).applyTo(label); domainCombo = new Combo(subDomainComp, SWT.BORDER | SWT.READ_ONLY); GridDataFactory.fillDefaults().grab(true, false).applyTo(domainCombo); domainCombo.setEnabled(true); domainCombo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { resolveUrlFromSubdomain(domainCombo); } }); label = new Label(subDomainComp, SWT.NONE); label.setText(Messages.CloudApplicationUrlPart_TEXT_DEPLOYURL_LABEL); GridDataFactory.fillDefaults().grab(false, false).align(SWT.FILL, SWT.CENTER).applyTo(label); fullURLText = new Text(subDomainComp, SWT.BORDER); GridDataFactory.fillDefaults().grab(true, false).applyTo(fullURLText); fullURLText.addModifyListener(new ModifyListener() { public void modifyText(ModifyEvent arg0) { currentUrl = fullURLText.getText(); validate(null, fullURLText); } }); return subDomainComp; } public String getCurrentDomain() { if (isActive(domainCombo)) { int selectionIndex = domainCombo.getSelectionIndex(); String[] domains = domainCombo.getItems(); if (selectionIndex >= 0 && selectionIndex < domains.length) { return domains[selectionIndex]; } } return null; } public void refreshDomains() { if (isActive(domainCombo)) { String existingSelection = getCurrentDomain(); List<String> domains = getDomains(); domainCombo.setItems(domains.toArray(new String[0])); updateDomainSelection(existingSelection); } } public void setUrl(String url) { // Update the current URL, whether valid or not currentUrl = url; setTextValue(fullURLText, currentUrl); } public void setSubdomain(String subdomain) { setTextValue(subDomainText, subdomain); } /** * Sets value in the given text control if the control is active , and there * is a change in the text control value. A null value will be set as an * empty String in the control (this serves as a way to "clear" the * control). * @param textControl where text needs to be set * @param value. If null, empty string will be set to clear the control */ protected void setTextValue(Text textControl, String value) { if (value == null) { value = ""; //$NON-NLS-1$ } // Only set value if change occurred to avoid unnecessary validation if (isActive(textControl) && !textControl.getText().equals(value)) { // Setting value will notify Control listener which then // triggers validation textControl.setText(value); } } protected void resolveUrlFromSubdomain(Control source) { String subdomain = subDomainText.getText(); String domain = getCurrentDomain(); CloudApplicationURL suggestedUrl = new CloudApplicationURL(subdomain, domain); validate(suggestedUrl, source); } protected boolean isActive(Control control) { return control != null && !control.isDisposed(); } /** * Validate a given application URL. If no application URL is specified, a * raw URL will be validated instead, if available. * * <p/> * If the URL is valid, meaning it has valid subdomain and domain segments: * * <p/> * 1. UI controls will be updated ONLY if there have been changes to the * control values * <p/> * 2. If there are changes to values, or error occurred during validation, * an event will be fired to notify any registered listeners * <p/> * @param appUrl * @param status */ protected void validate(CloudApplicationURL appUrl, Control source) { // Validation has already been requested by a control, therefore don't // start another // one to avoid recursive validations if (this.validationSource != null) { return; } this.validationSource = source; // If no Application URL is given with subdomain and domain values set, // attempt to parse the existing // current URL as it may have changed manually IStatus status = Status.OK_STATUS; if (appUrl == null) { if (currentUrl != null) { try { appUrl = lookupService.getCloudApplicationURL(currentUrl); } catch (CoreException ce) { status = ce.getStatus(); } } } else { try { appUrl = lookupService.validateCloudApplicationUrl(appUrl); } catch (CoreException ce) { status = ce.getStatus(); } } // Update the current URL regardless of whether the Application URL is // valid or not, to make sure UI controls are up-to-date if (appUrl != null) { currentUrl = appUrl.getUrl(); } if (this.validationSource != fullURLText) { setTextValue(fullURLText, currentUrl); } if (this.validationSource != domainCombo) { String domain = appUrl != null ? appUrl.getDomain() : null; updateDomainSelection(domain); } if (this.validationSource != subDomainText) { String subDomain = appUrl != null ? appUrl.getSubdomain() : null; setTextValue(subDomainText, subDomain); } validationSource = null; notifyChange(new PartChangeEvent(currentUrl, status, CloudUIEvent.APPLICATION_URL_CHANGED)); } /** * * @return non-null list of Domains. */ protected List<String> getDomains() { List<String> domains = new ArrayList<String>(); List<CloudDomain> cloudDomains = lookupService.getDomains(); if (cloudDomains != null) { for (CloudDomain cldm : cloudDomains) { domains.add(cldm.getName()); } } return domains; } protected void updateDomainSelection(String domain) { if (isActive(domainCombo)) { // If no domain is to be set, or it no longer exists in list of // domains (possibly because list of domains has been changed), // select // a default one if (getSelectionIndex(domain) < 0 && getCurrentDomain() == null) { List<String> domains = getDomains(); if (!domains.isEmpty()) { domain = domains.get(0); } } if (domain != null) { int selectionIndex = getSelectionIndex(domain); if (selectionIndex > -1) { domainCombo.select(selectionIndex); } } } } protected int getSelectionIndex(String domain) { int selectionIndex = -1; String[] domains = domainCombo.getItems(); if (domains != null) { for (int i = 0; i < domains.length; i++) { if (domains[i].equals(domain)) { selectionIndex = i; break; } } } return selectionIndex; } }