package com.amazonaws.eclipse.codedeploy.deploy.wizard.page;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newControlDecoration;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newFillingLabel;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newGroup;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newLabel;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newRadioButton;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newText;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.eclipse.core.databinding.AggregateValidationStatus;
import org.eclipse.core.databinding.Binding;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.observable.IChangeListener;
import org.eclipse.jface.databinding.swt.ISWTObservableValue;
import org.eclipse.jface.databinding.swt.SWTObservables;
import org.eclipse.jface.fieldassist.ControlDecoration;
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.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import com.amazonaws.eclipse.codedeploy.appspec.model.AppspecTemplateMetadataModel;
import com.amazonaws.eclipse.codedeploy.appspec.model.AppspecTemplateParameter;
import com.amazonaws.eclipse.codedeploy.deploy.wizard.page.validator.ContextPathValidator;
import com.amazonaws.eclipse.codedeploy.deploy.wizard.page.validator.GenericTemplateParameterValidator;
import com.amazonaws.eclipse.codedeploy.deploy.wizard.page.validator.ServerHttpPortValidator;
import com.amazonaws.eclipse.databinding.ChainValidator;
import com.amazonaws.eclipse.databinding.DecorationChangeListener;
/**
* The UI composite for displaying the configuration options for a specific
* appspec template. The composites for all the available templates are
* stacked onto the same stack-layout area.
*/
class AppspecTemplateConfigComposite extends Composite {
private static final String CONTEXT_PATH_ANCHOR_TEXT = "##CONTEXT_PATH##";
private static final String HTTP_PORT_ANCHOR_TEXT = "##HTTP_PORT##";
private static final String DEPLOY_TO_ROOT_ANCHOR_TEXT = "##DEPLOY_TO_ROOT##";
private static final String CONTEXT_PATH_DEFAULT = "application";
private static final String HTTP_PORT_DEFAULT = "8080";
/* Data model */
private final AppspecTemplateMetadataModel templateModel;
private final DataBindingContext bindingContext;
private final AggregateValidationStatus aggregateValidationStatus;
/*
* Special UI widgets for HTTP port and context-path configuration
*/
private Label serverUrlPreviewLabel;
private ISWTObservableValue deployToContextPathRadioButtonObservable;
private Text contextPathText;
private ISWTObservableValue contextPathTextObservable;
private Text httpPortText;
private ISWTObservableValue httpPortTextObservable;
/**
* UI widgets for generic parameters
*/
private final List<ParameterInputGroup> parameterInputGroups = new LinkedList<ParameterInputGroup>();
/**
* @see #setValidationStatusChangeListener(IChangeListener)
* @see #removeValidationStatusChangeListener()
*/
private IChangeListener validationStatusChangeListener;
public AppspecTemplateConfigComposite(Composite parent, int style,
AppspecTemplateMetadataModel templateModel) {
super(parent, style);
this.templateModel = templateModel;
this.bindingContext = new DataBindingContext();
this.aggregateValidationStatus = new AggregateValidationStatus(
bindingContext, AggregateValidationStatus.MAX_SEVERITY);
setLayout(new GridLayout(2, false));
createControls(this);
}
public AppspecTemplateMetadataModel getTemplateModel() {
return templateModel;
}
/**
* Set listener that will be notified whenever the validation status of this
* composite is updated. This method removes the listener (if any) that is
* currently registered to this composite - only one listener instance is
* allowed at a time.
*/
public synchronized void setValidationStatusChangeListener(IChangeListener listener) {
removeValidationStatusChangeListener();
validationStatusChangeListener = listener;
aggregateValidationStatus.addChangeListener(listener);
}
/**
* @see #setValidationStatusChangeListener(IChangeListener)
*/
public synchronized void removeValidationStatusChangeListener() {
if (validationStatusChangeListener != null) {
aggregateValidationStatus.removeChangeListener(validationStatusChangeListener);
validationStatusChangeListener = null;
}
}
public void updateValidationStatus() {
Iterator<?> iterator = bindingContext.getBindings().iterator();
while (iterator.hasNext()) {
Binding binding = (Binding)iterator.next();
binding.updateTargetToModel();
}
}
/**
* @return a map of all the template parameter values, keyed by the anchor
* text of each parameter.
*/
public Map<String, String> getAllParameterValues() {
Map<String, String> values = new HashMap<String, String>();
if (templateModel.isUseDefaultContextPathParameter()) {
values.put(DEPLOY_TO_ROOT_ANCHOR_TEXT,
(Boolean)deployToContextPathRadioButtonObservable.getValue()
? "false" : "true");
values.put(CONTEXT_PATH_ANCHOR_TEXT, (String)contextPathTextObservable.getValue());
}
if (templateModel.isUseDefaultHttpPortParameter()) {
values.put(HTTP_PORT_ANCHOR_TEXT, (String)httpPortTextObservable.getValue());
}
for (ParameterInputGroup genericParamInput : parameterInputGroups) {
values.put(
genericParamInput.getParameterModel().getSubstitutionAnchorText(),
genericParamInput.getParameterValue());
}
return values;
}
private void createControls(Composite parent) {
Label descriptionLabel = new Label(parent, SWT.NONE);
setItalicFont(descriptionLabel);
descriptionLabel.setText(
toUIString(templateModel.getTemplateDescription()));
if (templateModel.isUseDefaultContextPathParameter()
|| templateModel.isUseDefaultHttpPortParameter()) {
createServerUrlPreviewLabel(parent);
}
if (templateModel.isUseDefaultContextPathParameter()) {
createContextPathConfigurationSection(parent);
}
if (templateModel.isUseDefaultHttpPortParameter()) {
createHttpPortConfigurationSection(parent);
}
if (templateModel.getParameters() != null) {
for (AppspecTemplateParameter parameter : templateModel.getParameters()) {
ParameterInputGroup parameterInputGroup = new ParameterInputGroup(
parent, parameter, bindingContext);
parameterInputGroups.add(parameterInputGroup);
}
}
}
private static String toUIString(String str) {
return str == null ? "n/a" : str;
}
private void createServerUrlPreviewLabel(Composite composite) {
Group group = newGroup(composite, "", 2);
group.setLayout(new GridLayout(2, false));
newLabel(group, "Server URL:");
serverUrlPreviewLabel = newFillingLabel(group,
String.format("http://{ec2-public-dns}:%s/", HTTP_PORT_DEFAULT));
setBoldFont(serverUrlPreviewLabel);
Label label = newFillingLabel(group, "Your application will be available " +
"via this endpoint after the deployment.", 2);
setItalicFont(label);
}
private void refreshServerUrlPreviewLabel() {
boolean useContextPath = (Boolean) deployToContextPathRadioButtonObservable
.getValue();
String contextPath = contextPathText.getText();
String httpPort = httpPortText.getText();
serverUrlPreviewLabel.setText(
useContextPath
? String.format("http://{ec2-public-dns}:%s/%s/", httpPort, contextPath)
: String.format("http://{ec2-public-dns}:%s/", httpPort)
);
}
private void createContextPathConfigurationSection(Composite composite) {
Group group = newGroup(composite, "", 2);
group.setLayout(new GridLayout(2, false));
newRadioButton(group,
"Deploy application to server root", 2, true,
new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
contextPathText.setEnabled(false);
updateValidationStatus();
refreshServerUrlPreviewLabel();
}
}
);
Button deployToContextPathRadioButton = newRadioButton(group,
"Deploy application to context path", 1, false,
new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
contextPathText.setEnabled(true);
updateValidationStatus();
refreshServerUrlPreviewLabel();
}
}
);
deployToContextPathRadioButtonObservable = SWTObservables
.observeSelection(deployToContextPathRadioButton);
contextPathText = newText(group);
contextPathText.setEnabled(false);
contextPathText.setText(CONTEXT_PATH_DEFAULT);
contextPathTextObservable = SWTObservables.observeText(contextPathText, SWT.Modify);
bindingContext.bindValue(contextPathTextObservable, contextPathTextObservable);
ChainValidator<String> contextPathValidationProvider = new ChainValidator<String>(
contextPathTextObservable,
deployToContextPathRadioButtonObservable, //enabler
new ContextPathValidator("Invalid context path."));
bindingContext.addValidationStatusProvider(contextPathValidationProvider);
ControlDecoration contextPathTextDecoration = newControlDecoration(
contextPathText,
"Enter a valid context path for the application.");
new DecorationChangeListener(
contextPathTextDecoration,
contextPathValidationProvider.getValidationStatus());
contextPathText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
refreshServerUrlPreviewLabel();
}
});
}
private void createHttpPortConfigurationSection(Composite composite) {
Group group = newGroup(composite, "", 2);
group.setLayout(new GridLayout(2, false));
Label nameLabel = newLabel(group, "Application server HTTP port:");
nameLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
httpPortText = newText(group);
httpPortText.setText(HTTP_PORT_DEFAULT);
httpPortTextObservable = SWTObservables.observeText(httpPortText, SWT.Modify);
bindingContext.bindValue(httpPortTextObservable, httpPortTextObservable);
ChainValidator<String> httpPortValidationProvider = new ChainValidator<String>(
httpPortTextObservable,
new ServerHttpPortValidator("Invalid HTTP port."));
bindingContext.addValidationStatusProvider(httpPortValidationProvider);
ControlDecoration httpPortTextDecoration = newControlDecoration(
httpPortText,
"Enter a valid HTTP port for the Tomcat server.");
new DecorationChangeListener(
httpPortTextDecoration,
httpPortValidationProvider.getValidationStatus());
httpPortText.addModifyListener(new ModifyListener() {
public void modifyText(ModifyEvent e) {
refreshServerUrlPreviewLabel();
}
});
Label httpPortLabel = newFillingLabel(group,
"You might need to setup authbind in order to " +
"allow servlet container to listen on priviliged ports (0 - 1023).", 2);
setItalicFont(httpPortLabel);
}
private static class ParameterInputGroup extends Group {
private final AppspecTemplateParameter parameter;
private final DataBindingContext bindingContext;
private Text valueInputText;
private ControlDecoration valueInputTextDecoration;
private ISWTObservableValue valueInputTextObservable;
public ParameterInputGroup(Composite parent,
AppspecTemplateParameter parameter, DataBindingContext bindingContext) {
super(parent, SWT.NONE);
if (parameter == null) {
throw new NullPointerException("parameter must not be null.");
}
if (bindingContext == null) {
throw new NullPointerException("bindingContext must not be null.");
}
this.parameter = parameter;
this.bindingContext = bindingContext;
createControls(parent);
}
public AppspecTemplateParameter getParameterModel() {
return parameter;
}
public String getParameterValue() {
return (String)valueInputTextObservable.getValue();
}
private void createControls(Composite composite) {
GridData gridData = new GridData(SWT.FILL, SWT.TOP, true, false);
gridData.horizontalSpan = 2;
this.setLayoutData(gridData);
this.setLayout(new GridLayout(2, false));
this.setText(parameter.getName());
Label nameLabel = newLabel(this, parameter.getSubstitutionAnchorText());
nameLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
valueInputText = newText(this);
valueInputText.setText(parameter.getDefaultValueAsString());
valueInputTextObservable = SWTObservables.observeText(valueInputText, SWT.Modify);
bindingContext.bindValue(valueInputTextObservable, valueInputTextObservable);
ChainValidator<String> paramValidationStatusProvider = new ChainValidator<String>(
valueInputTextObservable,
new GenericTemplateParameterValidator(parameter));
bindingContext.addValidationStatusProvider(paramValidationStatusProvider);
valueInputTextDecoration = newControlDecoration(
valueInputText,
String.format("Invalid value for parameter %s (%s).",
parameter.getSubstitutionAnchorText(), parameter.getName()));
new DecorationChangeListener(
valueInputTextDecoration,
paramValidationStatusProvider.getValidationStatus());
}
@Override
protected void checkSubclass() {}
}
/*
* Font resources
*/
private Font italicFont;
private Font boldFont;
private void setItalicFont(Control control) {
FontData[] fontData = control.getFont()
.getFontData();
for (FontData fd : fontData) {
fd.setStyle(SWT.ITALIC);
}
italicFont = new Font(Display.getDefault(), fontData);
control.setFont(italicFont);
}
private void setBoldFont(Control control) {
FontData[] fontData = control.getFont()
.getFontData();
for (FontData fd : fontData) {
fd.setStyle(SWT.BOLD);
}
boldFont = new Font(Display.getDefault(), fontData);
control.setFont(boldFont);
}
@Override
public void dispose() {
if (italicFont != null)
italicFont.dispose();
if (boldFont != null)
boldFont.dispose();
super.dispose();
}
}