org.jboss.tools.openshift.internal.ui.wizard.deployimage.ServicesAndRoutingPage.java Source code

Java tutorial

Introduction

Here is the source code for org.jboss.tools.openshift.internal.ui.wizard.deployimage.ServicesAndRoutingPage.java

Source

/*******************************************************************************
 * Copyright (c) 2015-2016 Red Hat, Inc.
 * Distributed under license by Red Hat, Inc. All rights reserved.
 * This program is 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:
 *     Red Hat, Inc. - initial API and implementation
 ******************************************************************************/
package org.jboss.tools.openshift.internal.ui.wizard.deployimage;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.validator.routines.DomainValidator;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.beans.BeanProperties;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.property.Properties;
import org.eclipse.core.databinding.validation.MultiValidator;
import org.eclipse.core.databinding.validation.ValidationStatus;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.databinding.fieldassist.ControlDecorationSupport;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.jface.databinding.viewers.ObservableListContentProvider;
import org.eclipse.jface.databinding.viewers.ObservableMapLabelProvider;
import org.eclipse.jface.databinding.viewers.ViewerProperties;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.IElementComparer;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.window.Window;
import org.eclipse.jface.wizard.IWizard;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.Text;
import org.jboss.tools.common.ui.databinding.ParametrizableWizardPageSupport;
import org.jboss.tools.common.ui.databinding.ValueBindingBuilder;
import org.jboss.tools.openshift.internal.common.ui.TableCellMouseAdapter;
import org.jboss.tools.openshift.internal.common.ui.databinding.IsNotNull2BooleanConverter;
import org.jboss.tools.openshift.internal.common.ui.databinding.TrimmingStringConverter;
import org.jboss.tools.openshift.internal.common.ui.utils.TableViewerBuilder;
import org.jboss.tools.openshift.internal.common.ui.utils.UIUtils;
import org.jboss.tools.openshift.internal.common.ui.wizard.AbstractOpenShiftWizardPage;
import org.jboss.tools.openshift.internal.ui.OpenShiftImages;
import org.jboss.tools.openshift.internal.ui.OpenShiftUIMessages;

import com.openshift.restclient.model.IServicePort;

/**
 * Page to configure OpenShift services and routes
 * 
 * @author jeff.cantrill
 * @author Jeff Maury
 *
 */
public class ServicesAndRoutingPage extends AbstractOpenShiftWizardPage {
    private static final String PAGE_NAME = "Services && Routing Settings Page";
    private static final String PAGE_TITLE = "Services && Routing Settings";
    private static final String PAGE_DESCRIPTION = "";
    private static final int ROUTE_PORT_COLUMN_INDEX = 3;
    private IServiceAndRoutingPageModel model;

    TableViewer portsViewer;

    public ServicesAndRoutingPage(IWizard wizard, IServiceAndRoutingPageModel model) {
        super(PAGE_TITLE, PAGE_DESCRIPTION, PAGE_NAME, wizard);
        this.model = model;
    }

    @Override
    protected void doCreateControls(Composite parent, DataBindingContext dbc) {
        GridLayoutFactory.fillDefaults().margins(10, 10).applyTo(parent);
        createExposedPortsControl(parent, dbc);

        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.BEGINNING).grab(true, false)
                .applyTo(new Label(parent, SWT.SEPARATOR | SWT.HORIZONTAL));

        //routing
        Composite routingContainer = new Composite(parent, SWT.NONE);
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, false).applyTo(routingContainer);
        GridLayoutFactory.fillDefaults().margins(6, 6).numColumns(2).applyTo(routingContainer);

        Button btnAddRoute = new Button(routingContainer, SWT.CHECK);
        btnAddRoute.setText("Add Route");
        btnAddRoute.setToolTipText(
                "Adding a route to the service will make the image accessible\noutside of the OpenShift cluster on all the available service ports. \nYou can target a specific port by editing the route later.");
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(false, false).span(2, 1).applyTo(btnAddRoute);
        final IObservableValue<Boolean> addRouteModelObservable = BeanProperties
                .value(IServiceAndRoutingPageModel.PROPERTY_ADD_ROUTE).observe(model);
        ValueBindingBuilder.bind(WidgetProperties.selection().observe(btnAddRoute)).to(addRouteModelObservable)
                .in(dbc);

        Label labelRouteHostname = new Label(routingContainer, SWT.NONE);
        labelRouteHostname.setText("Hostname:");
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).applyTo(labelRouteHostname);

        Text textRouteHostname = new Text(routingContainer, SWT.BORDER);
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).grab(true, false).applyTo(textRouteHostname);
        ValueBindingBuilder.bind(WidgetProperties.enabled().observe(textRouteHostname))
                .to(BeanProperties.value(IServiceAndRoutingPageModel.PROPERTY_ADD_ROUTE).observe(model)).in(dbc);
        final IObservableValue<String> routeHostnameObservable = WidgetProperties.text(SWT.Modify)
                .observe(textRouteHostname);
        ValueBindingBuilder.bind(routeHostnameObservable).converting(new TrimmingStringConverter())
                .to(BeanProperties.value(IServiceAndRoutingPageModel.PROPERTY_ROUTE_HOSTNAME).observe(model))
                .in(dbc);

        MultiValidator validator = new MultiValidator() {

            @Override
            protected IStatus validate() {
                IStatus status = ValidationStatus.ok();
                boolean isAddRoute = addRouteModelObservable.getValue();
                String hostName = routeHostnameObservable.getValue();
                final IObservableList<IServicePort> portsObservable = BeanProperties
                        .list(IServiceAndRoutingPageModel.PROPERTY_SERVICE_PORTS).observe(model);
                final IServicePort routingPort = (IServicePort) BeanProperties
                        .value(IServiceAndRoutingPageModel.PROPERTY_ROUTING_PORT).observe(model).getValue();

                if (isAddRoute) {
                    if (StringUtils.isBlank(hostName)) {
                        status = ValidationStatus
                                .info(NLS.bind(OpenShiftUIMessages.EmptyHostNameErrorMessage, hostName));
                    } else if (!DomainValidator.getInstance(true).isValid(hostName)) {
                        status = ValidationStatus
                                .error(NLS.bind(OpenShiftUIMessages.InvalidHostNameErrorMessage, hostName));
                    }
                    if (!status.matches(IStatus.ERROR) && isAddRoute && (portsObservable.size() > 1)
                            && (routingPort == null)) {
                        if (status.matches(IStatus.INFO)) {
                            status = ValidationStatus.info(
                                    status.getMessage() + "\n " + OpenShiftUIMessages.RoundRobinRoutingMessage);
                        } else {
                            status = ValidationStatus.info(OpenShiftUIMessages.RoundRobinRoutingMessage);
                        }
                    }
                }
                return status;
            }
        };
        dbc.addValidationStatusProvider(validator);
        ControlDecorationSupport.create(validator, SWT.LEFT | SWT.TOP);
    }

    private void createExposedPortsControl(Composite parent, DataBindingContext dbc) {
        Composite container = new Composite(parent, SWT.NONE);
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, false).applyTo(container);
        GridLayoutFactory.fillDefaults().numColumns(2).margins(6, 6).applyTo(container);

        Label label = new Label(container, SWT.NONE);
        label.setText("Service Ports:");
        label.setToolTipText("The exposed ports of the image.");
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).span(2, 1).applyTo(label);
        Composite tableContainer = new Composite(container, SWT.NONE);

        IObservableList<IServicePort> portsObservable = BeanProperties
                .list(IServiceAndRoutingPageModel.PROPERTY_SERVICE_PORTS).observe(model);
        portsViewer = createTable(tableContainer);

        ObservableListContentProvider contentProvider = new ObservableListContentProvider();
        portsViewer.setContentProvider(contentProvider);
        ObservableMapLabelProvider labelProvider = new ObservableMapLabelProvider(
                Properties.observeEach(contentProvider.getKnownElements(),
                        BeanProperties.values(ServicePortAdapter.NAME, ServicePortAdapter.PORT,
                                ServicePortAdapter.TARGET_PORT,
                                /* ROUTE_PORT_COLUMN_INDEX = 3 */ ServicePortAdapter.ROUTE_PORT))) {
            @Override
            public Image getColumnImage(Object element, int columnIndex) {
                if (columnIndex == ROUTE_PORT_COLUMN_INDEX) {
                    Object selected = attributeMaps[columnIndex].get(element);
                    return selected != null && (boolean) selected ? OpenShiftImages.CHECKED_IMG
                            : OpenShiftImages.UNCHECKED_IMG;
                }
                return null;
            }

            @Override
            public String getColumnText(Object element, int columnIndex) {
                if (columnIndex != ROUTE_PORT_COLUMN_INDEX) {
                    Object result = attributeMaps[columnIndex].get(element);
                    return result == null ? "" : result.toString(); //$NON-NLS-1$
                }
                return null;
            }
        };
        portsViewer.setLabelProvider(labelProvider);
        GridDataFactory.fillDefaults().span(1, 5).align(SWT.FILL, SWT.FILL).grab(true, true)
                .applyTo(tableContainer);
        ValueBindingBuilder.bind(ViewerProperties.singleSelection().observe(portsViewer))
                .to(BeanProperties.value(IServiceAndRoutingPageModel.PROPERTY_SELECTED_SERVICE_PORT).observe(model))
                .in(dbc);

        portsViewer.setInput(portsObservable);
        dbc.addValidationStatusProvider(new MultiValidator() {

            @Override
            protected IStatus validate() {
                if (portsObservable.isEmpty()) {
                    return ValidationStatus.error(
                            "At least 1 port is required when generating the service for the deployed image");
                }
                return Status.OK_STATUS;
            }
        });

        portsViewer.getTable().addMouseListener(onTableCellClicked());

        Button btnEdit = new Button(container, SWT.PUSH);
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).applyTo(btnEdit);
        btnEdit.setText("Edit...");
        btnEdit.setToolTipText("Edit a port to be exposed by the service.");
        btnEdit.addSelectionListener(new EditHandler());
        ValueBindingBuilder.bind(WidgetProperties.enabled().observe(btnEdit)).notUpdatingParticipant()
                .to(BeanProperties.value(IServiceAndRoutingPageModel.PROPERTY_SELECTED_SERVICE_PORT).observe(model))
                .converting(new IsNotNull2BooleanConverter()).in(dbc);
        UIUtils.setDefaultButtonWidth(btnEdit);

        Button btnReset = new Button(container, SWT.PUSH);
        GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).applyTo(btnReset);
        btnReset.setText("Reset");
        btnReset.setToolTipText("Resets the list of ports to the exposed ports of the image.");
        btnReset.addSelectionListener(onReset());
        UIUtils.setDefaultButtonWidth(btnReset);

    }

    private MouseListener onTableCellClicked() {
        return new TableCellMouseAdapter(ROUTE_PORT_COLUMN_INDEX) {

            @Override
            public void mouseUpCell(MouseEvent event) {
                IServicePort port = model.getSelectedServicePort();
                ServicePortAdapter target = new ServicePortAdapter((ServicePortAdapter) port);
                target.setRoutePort(!target.isRoutePort());
                target.setName(NLS.bind("{0}-tcp", target.getPort()));
                model.updateServicePort(port, target);
                model.setSelectedServicePort(target);
                Display.getDefault().asyncExec(() -> {
                    if (portsViewer != null && portsViewer.getTable() != null
                            && !portsViewer.getTable().isDisposed()) {
                        portsViewer.refresh();
                    }
                });
            }
        };
    }

    class EditHandler extends SelectionAdapter implements IDoubleClickListener {

        @Override
        public void doubleClick(DoubleClickEvent event) {
            handleEvent();
        }

        @Override
        public void widgetSelected(SelectionEvent e) {
            handleEvent();
        }

        public void handleEvent() {
            String message = "Edit the port to be exposed by the service";
            final IServicePort port = model.getSelectedServicePort();
            final ServicePortAdapter target = new ServicePortAdapter((ServicePortAdapter) port);
            ServicePortDialog dialog = new ServicePortDialog(target, message, model.getServicePorts());
            if (Window.OK == dialog.open()) {
                target.setName(NLS.bind("{0}-tcp", target.getPort()));
                model.updateServicePort(port, target);
                model.setSelectedServicePort(target);
            }
        }
    }

    protected TableViewer createTable(Composite tableContainer) {
        Table table = new Table(tableContainer, SWT.BORDER | SWT.FULL_SELECTION | SWT.V_SCROLL | SWT.H_SCROLL);
        table.setLinesVisible(true);
        table.setHeaderVisible(true);

        TableViewer viewer = new TableViewerBuilder(table, tableContainer).column("Name").align(SWT.LEFT).weight(2)
                .minWidth(50).buildColumn().column("Service Port").align(SWT.LEFT).weight(1).minWidth(25)
                .buildColumn().column("Pod Port").align(SWT.LEFT).weight(1).minWidth(25).buildColumn()
                .column(new ColumnLabelProvider() {
                    @Override
                    public Image getImage(Object element) {
                        boolean selected = ((ServicePortAdapter) element).isRoutePort();
                        return selected ? OpenShiftImages.CHECKED_IMG : OpenShiftImages.UNCHECKED_IMG;
                    }

                    @Override
                    public String getText(Object element) {
                        return null;
                    }

                }).name("Used by route").align(SWT.LEFT).weight(1).buildColumn().buildViewer();
        viewer.addDoubleClickListener(new EditHandler());
        /*
         * required because otherwise values are cached and causes the ObservableMapLabelProvider
         * not to be updated because remove are not propagated.
         */
        viewer.setComparer(new IElementComparer() {

            @Override
            public int hashCode(Object element) {
                return System.identityHashCode(element);
            }

            @Override
            public boolean equals(Object a, Object b) {
                return a == b;
            }
        });
        return viewer;
    }

    private SelectionListener onReset() {
        return new SelectionAdapter() {

            @Override
            public void widgetSelected(SelectionEvent e) {
                if (MessageDialog.openQuestion(getShell(), "Reset ports",
                        "Are you sure you want to reset the serviced ports to those exposed by the image?")) {
                    model.resetServicePorts();
                }
            }

        };
    }

    /**
     * Allow Finish for info statuses.
     */
    @Override
    protected void setupWizardPageSupport(DataBindingContext dbc) {
        ParametrizableWizardPageSupport.create(IStatus.ERROR | IStatus.CANCEL, this, dbc);
    }

}