org.apache.ace.webui.vaadin.VaadinClient.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.ace.webui.vaadin.VaadinClient.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.ace.webui.vaadin;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.ace.authentication.api.AuthenticationService;
import org.apache.ace.client.repository.RepositoryAdmin;
import org.apache.ace.client.repository.RepositoryAdminLoginContext;
import org.apache.ace.client.repository.RepositoryObject;
import org.apache.ace.client.repository.SessionFactory;
import org.apache.ace.client.repository.helper.bundle.BundleHelper;
import org.apache.ace.client.repository.object.Artifact2FeatureAssociation;
import org.apache.ace.client.repository.object.ArtifactObject;
import org.apache.ace.client.repository.object.Distribution2TargetAssociation;
import org.apache.ace.client.repository.object.DistributionObject;
import org.apache.ace.client.repository.object.Feature2DistributionAssociation;
import org.apache.ace.client.repository.object.FeatureObject;
import org.apache.ace.client.repository.object.TargetObject;
import org.apache.ace.client.repository.repository.Artifact2FeatureAssociationRepository;
import org.apache.ace.client.repository.repository.ArtifactRepository;
import org.apache.ace.client.repository.repository.ArtifactRepository.ArtifactAlreadyExistsException;
import org.apache.ace.client.repository.repository.Distribution2TargetAssociationRepository;
import org.apache.ace.client.repository.repository.DistributionRepository;
import org.apache.ace.client.repository.repository.Feature2DistributionAssociationRepository;
import org.apache.ace.client.repository.repository.FeatureRepository;
import org.apache.ace.client.repository.repository.TargetRepository;
import org.apache.ace.client.repository.stateful.StatefulTargetObject;
import org.apache.ace.client.repository.stateful.StatefulTargetRepository;
import org.apache.ace.connectionfactory.ConnectionFactory;
import org.apache.ace.webui.NamedObject;
import org.apache.ace.webui.UIExtensionFactory;
import org.apache.ace.webui.domain.NamedStatefulTargetObject;
import org.apache.ace.webui.domain.NamedTargetObject;
import org.apache.ace.webui.vaadin.LoginWindow.LoginFunction;
import org.apache.ace.webui.vaadin.UploadHelper.ArtifactDropHandler;
import org.apache.ace.webui.vaadin.UploadHelper.GenericUploadHandler;
import org.apache.ace.webui.vaadin.UploadHelper.UploadHandle;
import org.apache.ace.webui.vaadin.component.ArtifactsPanel;
import org.apache.ace.webui.vaadin.component.AssociationHelper;
import org.apache.ace.webui.vaadin.component.DistributionsPanel;
import org.apache.ace.webui.vaadin.component.FeaturesPanel;
import org.apache.ace.webui.vaadin.component.MainActionToolbar;
import org.apache.ace.webui.vaadin.component.StatusLine;
import org.apache.ace.webui.vaadin.component.TargetsPanel;
import org.apache.felix.dm.Component;
import org.apache.felix.dm.DependencyManager;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
import org.osgi.service.log.LogService;
import org.osgi.service.useradmin.Authorization;
import org.osgi.service.useradmin.User;
import org.osgi.service.useradmin.UserAdmin;

import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.Property.ValueChangeListener;
import com.vaadin.event.ShortcutAction.KeyCode;
import com.vaadin.event.ShortcutAction.ModifierKey;
import com.vaadin.service.ApplicationContext;
import com.vaadin.terminal.gwt.server.WebApplicationContext;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.DragAndDropWrapper;
import com.vaadin.ui.DragAndDropWrapper.DragStartMode;
import com.vaadin.ui.GridLayout;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.ProgressIndicator;
import com.vaadin.ui.Window;
import com.vaadin.ui.Window.Notification;

/**
 * Main application entry point.
 */
@SuppressWarnings("serial")
public class VaadinClient extends com.vaadin.Application implements AssociationManager, LoginFunction {

    // basic session ID generator
    private static long generateSessionID() {
        return SESSION_ID.getAndIncrement();
    }

    /**
     * Remove the given directory and all it's files and subdirectories
     * 
     * @param directory
     *            the name of the directory to remove
     */
    private static void removeDirectoryWithContent(File directory) {
        if ((directory == null) || !directory.exists()) {
            return;
        }
        File[] filesAndSubDirs = directory.listFiles();
        for (int i = 0; i < filesAndSubDirs.length; i++) {
            File file = filesAndSubDirs[i];
            if (file.isDirectory()) {
                removeDirectoryWithContent(file);
            }
            // else just remove the file
            file.delete();
        }
        directory.delete();
    }

    private static final long serialVersionUID = 1L;
    private static final AtomicLong SESSION_ID = new AtomicLong(1L);
    private static final String targetRepo = "target";
    private static final String shopRepo = "shop";

    private static final String deployRepo = "deployment";

    private static final String customerName = "apache";

    private static final String endpoint = "/repository";

    private volatile AuthenticationService m_authenticationService;
    private volatile BundleContext m_context;
    private volatile SessionFactory m_sessionFactory;
    private volatile UserAdmin m_userAdmin;
    private volatile ArtifactRepository m_artifactRepository;
    private volatile FeatureRepository m_featureRepository;
    private volatile DistributionRepository m_distributionRepository;
    private volatile StatefulTargetRepository m_statefulTargetRepository;
    private volatile TargetRepository m_targetRepository;
    private volatile Artifact2FeatureAssociationRepository m_artifact2featureAssociationRepository;
    private volatile Feature2DistributionAssociationRepository m_feature2distributionAssociationRepository;
    private volatile Distribution2TargetAssociationRepository m_distribution2targetAssociationRepository;

    private volatile RepositoryAdmin m_admin;
    private volatile LogService m_log;
    private volatile ConnectionFactory m_connectionFactory;

    private String m_sessionID;

    private ArtifactsPanel m_artifactsPanel;
    private FeaturesPanel m_featuresPanel;
    private DistributionsPanel m_distributionsPanel;
    private TargetsPanel m_targetsPanel;
    private GridLayout m_grid;
    private StatusLine m_statusLine;
    private File m_sessionDir; // private folder for session info
    private HorizontalLayout m_artifactToolbar;
    private HorizontalLayout m_featureToolbar;
    private HorizontalLayout m_distributionToolbar;
    private HorizontalLayout m_targetToolbar;
    private Window m_mainWindow;
    private final URL m_obrUrl;
    private final String m_repositoryXML;

    private final URL m_repository;
    private final boolean m_useAuth;
    private final String m_userName;
    private final AssociationHelper m_associations = new AssociationHelper();
    private final AtomicBoolean m_dependenciesResolved = new AtomicBoolean(false);
    // for the artifacts list...
    private final double m_cacheRate;
    private final int m_pageLength;

    private ProgressIndicator m_progress;

    private DependencyManager m_manager;

    private Component m_component;

    private final List<Component> m_eventHandlers = new ArrayList<>();

    private GridLayout m_mainToolbar;

    /**
     * Creates a new {@link VaadinClient} instance.
     * 
     * @param m_manager2
     * 
     * @param aceHost
     *            the hostname where the management service can be reached;
     * @param obrUrl
     *            the URL of the OBR to use;
     * @param useAuth
     *            <code>true</code> to use authentication, <code>false</code> to disable authentication;
     * @param userName
     *            the hardcoded username to use when authentication is disabled.
     */
    public VaadinClient(DependencyManager manager, URL aceHost, URL obrUrl, String repositoryXML, boolean useAuth,
            String userName, String password, double cacheRate, int pageLength) {
        m_manager = manager;
        try {
            m_repository = new URL(aceHost, endpoint);
        } catch (MalformedURLException e) {
            throw new IllegalArgumentException("Need a valid repository URL!", e);
        }
        m_obrUrl = obrUrl;
        m_repositoryXML = repositoryXML;
        m_useAuth = useAuth;
        m_userName = userName;
        m_cacheRate = cacheRate;
        m_pageLength = pageLength;

        if (!m_useAuth && (m_userName == null || "".equals(m_userName.trim()))) {
            throw new IllegalArgumentException("Need a valid user name when no authentication is used!");
        }
    }

    @Override
    public void close() {
        if (isRunning()) {
            m_admin.deleteLocal();
            cleanupListeners();
            m_manager.remove(m_component);
            super.close();
        }
    }

    @Override
    public Artifact2FeatureAssociation createArtifact2FeatureAssociation(String artifactId, String featureId) {
        boolean dynamicRelation = false;

        FeatureObject feature = m_featureRepository.get(featureId);
        ArtifactObject artifact = m_artifactRepository.get(artifactId);
        if (artifact == null) {
            // Maybe a BSN?
            try {
                List<ArtifactObject> artifacts = m_artifactRepository.get(FrameworkUtil
                        .createFilter(String.format("(%s=%s)", Constants.BUNDLE_SYMBOLICNAME, artifactId)));
                if (artifacts != null && artifacts.size() > 0) {
                    dynamicRelation = true;
                    // we only need this artifact for creating the association, so it does not matter which one we
                    // take...
                    artifact = artifacts.get(0);
                }
            } catch (InvalidSyntaxException exception) {
                m_log.log(LogService.LOG_ERROR, "Invalid filter syntax?!", exception);
            }
        }

        // Make sure we didn't drop on a resource processor bundle...
        if (artifact != null && artifact.getAttribute(BundleHelper.KEY_RESOURCE_PROCESSOR_PID) != null) {
            // if you drop on a resource processor, and try to get it, you
            // will get null because you cannot associate anything with a
            // resource processor so we check for null here
            return null;
        }

        Artifact2FeatureAssociation result = null;
        if (artifact != null) {
            if (dynamicRelation) {
                Map<String, String> properties = Collections
                        .singletonMap(BundleHelper.KEY_ASSOCIATION_VERSIONSTATEMENT, "0.0.0");
                result = m_artifact2featureAssociationRepository.create(artifact, properties, feature, null);
            } else {
                result = m_artifact2featureAssociationRepository.create(artifact, feature);
            }
        }
        return result;
    }

    @Override
    public Distribution2TargetAssociation createDistribution2TargetAssociation(String distributionId,
            String targetId) {
        DistributionObject distribution = m_distributionRepository.get(distributionId);
        StatefulTargetObject target = m_statefulTargetRepository.get(targetId);
        if (!target.isRegistered()) {
            target.register();
            target.setAutoApprove(true);
        }
        return m_distribution2targetAssociationRepository.create(distribution, target.getTargetObject());
    }

    @Override
    public Feature2DistributionAssociation createFeature2DistributionAssociation(String featureId,
            String distributionId) {
        FeatureObject feature = m_featureRepository.get(featureId);
        DistributionObject distribution = m_distributionRepository.get(distributionId);
        return m_feature2distributionAssociationRepository.create(feature, distribution);
    }

    public void destroyDependencies() {
        m_sessionFactory.destroySession(m_sessionID);
        removeDirectoryWithContent(m_sessionDir);
    }

    public void init() {
        setTheme("ace");

        if (!m_dependenciesResolved.get()) {
            final Window message = new Window("Apache ACE");
            message.getContent().setSizeFull();
            setMainWindow(message);

            Label richText = new Label("<h1>Apache ACE User Interface</h1>"
                    + "<p>Due to missing component dependencies on the server, probably due to misconfiguration, "
                    + "the user interface cannot be properly started. Please contact your server administrator. "
                    + "You can retry accessing the user interface by <a href=\"?restartApplication\">following this link</a>.</p>");
            richText.setContentMode(Label.CONTENT_XHTML);

            // TODO we might want to add some more details here as to what's
            // missing on the other hand, the user probably can't fix that anyway
            message.addComponent(richText);
            return;
        }

        m_mainWindow = new Window("Apache ACE");
        m_mainWindow.getContent().setSizeFull();
        m_mainWindow.setBorder(Window.BORDER_NONE);

        setMainWindow(m_mainWindow);

        // Authenticate the user either by showing a login window; or by another means...
        authenticate();
    }

    /**
     * {@inheritDoc}
     */
    public boolean login(String username, String password) {
        setUser(m_authenticationService.authenticate(username, password));
        return doLogin();
    }

    /**
     * {@inheritDoc}
     */
    public void removeAssociation(Artifact2FeatureAssociation association) {
        m_artifact2featureAssociationRepository.remove(association);
    }

    /**
     * {@inheritDoc}
     */
    public void removeAssociation(Distribution2TargetAssociation association) {
        m_distribution2targetAssociationRepository.remove(association);
    }

    /**
     * {@inheritDoc}
     */
    public void removeAssociation(Feature2DistributionAssociation association) {
        m_feature2distributionAssociationRepository.remove(association);
    }

    public void setupDependencies(Component component) {
        m_sessionID = "web-" + generateSessionID();
        File dir = m_context.getDataFile(m_sessionID);
        dir.mkdir();
        m_sessionDir = dir.getAbsoluteFile();
        m_sessionFactory.createSession(m_sessionID, null);
        addSessionDependency(component, RepositoryAdmin.class);
        addSessionDependency(component, DistributionRepository.class);
        addSessionDependency(component, ArtifactRepository.class);
        addSessionDependency(component, FeatureRepository.class);
        addSessionDependency(component, Artifact2FeatureAssociationRepository.class);
        addSessionDependency(component, Feature2DistributionAssociationRepository.class);
        addSessionDependency(component, Distribution2TargetAssociationRepository.class);
        addSessionDependency(component, TargetRepository.class);
        addSessionDependency(component, StatefulTargetRepository.class);
        addDependency(component, ConnectionFactory.class);
    }

    public void start() {
        m_log.log(LogService.LOG_INFO, "Starting session #" + m_sessionID);
        m_dependenciesResolved.set(true);
    }

    @Override
    public void start(URL applicationUrl, Properties applicationProperties, ApplicationContext context) {
        m_component = m_manager.createComponent().setImplementation(this)
                .setCallbacks("setupDependencies", "start", "stop", "destroyDependencies")
                .add(m_manager.createServiceDependency().setService(SessionFactory.class).setRequired(true))
                .add(m_manager.createServiceDependency().setService(UserAdmin.class).setRequired(true))
                .add(m_manager.createServiceDependency().setService(AuthenticationService.class)
                        .setRequired(m_useAuth))
                .add(m_manager.createServiceDependency().setService(LogService.class).setRequired(false));
        m_manager.add(m_component);
        super.start(applicationUrl, applicationProperties, context);
    }

    public void stop() throws Exception {
        m_log.log(LogService.LOG_INFO, "Stopping session #" + m_sessionID);

        try {
            close();

            try {
                m_admin.logout(true /* force */);
            } catch (IllegalStateException exception) {
                // Ignore, we're already logged out...
            }
        } finally {
            m_dependenciesResolved.set(false);
        }
    }

    final void showAddArtifactDialog() {
        final AddArtifactWindow window = new AddArtifactWindow(m_sessionDir, m_obrUrl, m_repositoryXML) {
            @Override
            protected ArtifactRepository getArtifactRepository() {
                return m_artifactRepository;
            }

            @Override
            protected ConnectionFactory getConnectionFactory() {
                return m_connectionFactory;
            }

            @Override
            protected LogService getLogger() {
                return m_log;
            }
        };

        // Open the subwindow by adding it to the parent window
        window.showWindow(getMainWindow());
    }

    final void showManageResourceProcessorsDialog() {
        ManageResourceProcessorWindow window = new ManageResourceProcessorWindow() {
            @Override
            protected ArtifactRepository getArtifactRepository() {
                return m_artifactRepository;
            }
        };
        // Open the subwindow by adding it to the parent window
        window.showWindow(getMainWindow());
    }

    /**
     * Create a new distribution in the distribution repository
     * 
     * @param name
     *            the name of the new distribution;
     * @param description
     *            the description of the new distribution.
     */
    protected DistributionObject createDistribution(String name, String description) {
        Map<String, String> attributes = new HashMap<>();
        attributes.put(DistributionObject.KEY_NAME, name);
        attributes.put(DistributionObject.KEY_DESCRIPTION, description);
        Map<String, String> tags = new HashMap<>();
        return m_distributionRepository.create(attributes, tags);
    }

    /**
     * Create a new feature in the feature repository.
     * 
     * @param name
     *            the name of the new feature;
     * @param description
     *            the description of the new feature.
     */
    protected FeatureObject createFeature(String name, String description) {
        Map<String, String> attributes = new HashMap<>();
        attributes.put(FeatureObject.KEY_NAME, name);
        attributes.put(FeatureObject.KEY_DESCRIPTION, description);
        Map<String, String> tags = new HashMap<>();
        return m_featureRepository.create(attributes, tags);
    }

    /**
     * Create a new target in the stateful target repository.
     * 
     * @param name
     *            the name of the new target;
     */
    protected StatefulTargetObject createTarget(String name) {
        Map<String, String> attributes = new HashMap<>();
        attributes.put(StatefulTargetObject.KEY_ID, name);
        attributes.put(TargetObject.KEY_AUTO_APPROVE, "true");
        Map<String, String> tags = new HashMap<>();
        return m_statefulTargetRepository.preregister(attributes, tags);
    }

    private void addCrossPlatformAddShortcut(Button button, int keycode, String description) {
        // ACE-427 - NPE when using getMainWindow() if no authentication is used...
        WebApplicationContext context = (WebApplicationContext) getContext();
        ShortcutHelper.addCrossPlatformShortcut(context.getBrowser(), button, description, keycode,
                ModifierKey.SHIFT);
    }

    private void addDependency(Component component, Class<?> service) {
        component.add(m_manager.createServiceDependency().setService(service).setRequired(true));
    }

    private void addListener(final Object implementation, final String... topics) {
        Properties props = new Properties();
        props.put(EventConstants.EVENT_TOPIC, topics);
        props.put(EventConstants.EVENT_FILTER, "(" + SessionFactory.SERVICE_SID + "=" + m_sessionID + ")");
        Component component = m_manager.createComponent().setInterface(EventHandler.class.getName(), props)
                .setImplementation(implementation);
        synchronized (m_eventHandlers) {
            m_eventHandlers.add(component);
        }
        m_manager.add(component);
    }

    private void addSessionDependency(Component component, Class<?> service) {
        component.add(m_manager.createServiceDependency()
                .setService(service, "(" + SessionFactory.SERVICE_SID + "=" + m_sessionID + ")").setRequired(true));
    }

    /**
     * Determines how authentication should take place.
     */
    private void authenticate() {
        if (m_useAuth) {
            showLoginWindow();
        } else {
            // Not using authentication; use fallback scenario...
            loginAutomatically();
        }
    }

    private void cleanupListeners() {
        Component[] components;
        synchronized (m_eventHandlers) {
            components = m_eventHandlers.toArray(new Component[m_eventHandlers.size()]);
            m_eventHandlers.clear();
        }
        for (Component component : components) {
            m_manager.remove(component);
        }
    }

    /**
     * Create a button to show a pop window for adding new features.
     * 
     * @param user
     * 
     * @param main
     *            Main Window
     * @return Button
     */
    private Button createAddArtifactButton() {
        Button button = new Button("+");
        addCrossPlatformAddShortcut(button, KeyCode.A, "Add a new artifact");
        button.addListener(new Button.ClickListener() {
            public void buttonClick(ClickEvent event) {
                showAddArtifactDialog();
            }
        });
        return button;
    }

    /**
     * Create a button to show a popup window for adding a new distribution. On success this calls the
     * createDistribution() method.
     * 
     * @param user
     * 
     * @return the add-distribution button instance.
     */
    private Button createAddDistributionButton() {
        Button button = new Button("+");
        addCrossPlatformAddShortcut(button, KeyCode.D, "Add a new distribution");
        button.addListener(new Button.ClickListener() {
            public void buttonClick(ClickEvent event) {
                GenericAddWindow window = new GenericAddWindow("Add Distribution") {
                    public void handleError(Exception e) {
                        // ACE-241: notify user when the distribution-creation failed!
                        getWindow().showNotification("Failed to add new distribution!",
                                "<br/>Reason: " + e.getMessage(), Notification.TYPE_ERROR_MESSAGE);
                    }

                    public void onOk(String name, String description) {
                        createDistribution(name, description);
                    }
                };
                window.show(getMainWindow());
            }
        });

        return button;
    }

    /***
     * Create a button to show popup window for adding a new feature. On success this calls the createFeature() method.
     * 
     * @param user
     * 
     * @return the add-feature button instance.
     */
    private Button createAddFeatureButton() {
        Button button = new Button("+");
        addCrossPlatformAddShortcut(button, KeyCode.F, "Add a new feature");
        button.addListener(new Button.ClickListener() {
            public void buttonClick(ClickEvent event) {
                GenericAddWindow window = new GenericAddWindow("Add Feature") {
                    public void handleError(Exception e) {
                        // ACE-241: notify user when the feature-creation failed!
                        getWindow().showNotification("Failed to add new feature!", "<br/>Reason: " + e.getMessage(),
                                Notification.TYPE_ERROR_MESSAGE);
                    }

                    public void onOk(String name, String description) {
                        createFeature(name, description);
                    }
                };
                window.show(getMainWindow());
            }
        });
        return button;
    }

    /**
     * Create a button to show a popup window for adding a new target. On success this calls the createTarget() method
     * 
     * @param user
     * 
     * @return the add-target button instance.
     */
    private Button createAddTargetButton() {
        Button button = new Button("+");
        addCrossPlatformAddShortcut(button, KeyCode.G, "Add a new target");
        button.addListener(new Button.ClickListener() {
            public void buttonClick(ClickEvent event) {
                GenericAddWindow window = new GenericAddWindow("Add Target") {
                    protected void handleError(Exception e) {
                        // ACE-241: notify user when the target-creation failed!
                        getWindow().showNotification("Failed to add new target!", "<br/>Reason: " + e.getMessage(),
                                Notification.TYPE_ERROR_MESSAGE);
                    }

                    @Override
                    protected void initDialog() {
                        m_name.setCaption("Identifier");
                        m_description.setVisible(false);

                        super.initDialog();
                    }

                    protected void onOk(String id, String description) {
                        createTarget(id);
                    }
                };
                window.show(getMainWindow());
            }
        });
        return button;
    }

    /**
     * @return a button to approve one or more targets.
     */
    private Button createApproveTargetsButton() {
        final Button button = new Button("A");
        button.setDisableOnClick(true);
        button.setImmediate(true);
        button.setEnabled(false);
        button.addListener(new Button.ClickListener() {
            @Override
            public void buttonClick(ClickEvent event) {
                m_targetsPanel.approveSelectedTargets();
            }
        });
        m_targetsPanel.addListener(new ValueChangeListener() {
            @Override
            public void valueChange(ValueChangeEvent event) {
                TargetsPanel targetsPanel = (TargetsPanel) event.getProperty();

                Collection<?> itemIDs = (Collection<?>) targetsPanel.getValue();

                boolean enabled = false;
                for (Object itemID : itemIDs) {
                    if (targetsPanel.isItemApproveNeeded(itemID)) {
                        enabled = true;
                        break;
                    }
                }

                button.setEnabled(enabled);
            }
        });
        return button;
    }

    private ArtifactsPanel createArtifactsPanel() {
        return new ArtifactsPanel(m_associations, this, m_cacheRate, m_pageLength) {
            @Override
            protected EditWindow createEditor(final NamedObject object, final List<UIExtensionFactory> extensions) {
                return new EditWindow("Edit Artifact", object, extensions) {
                    @Override
                    protected void handleError(Exception e) {
                        getWindow().showNotification("Failed to edit artifact!", "<br/>Reason: " + e.getMessage(),
                                Notification.TYPE_ERROR_MESSAGE);
                    }

                    @Override
                    protected void onOk(String name, String description) throws Exception {
                        object.setDescription(description);
                    }
                };
            }

            @Override
            protected ArtifactRepository getRepository() {
                return m_artifactRepository;
            }

            @Override
            protected RepositoryAdmin getRepositoryAdmin() {
                return m_admin;
            }
        };
    }

    private HorizontalLayout createArtifactToolbar() {
        HorizontalLayout result = new HorizontalLayout();
        result.setSpacing(true);
        result.addComponent(createAddArtifactButton());
        result.addComponent(createManageResourceProcessorsButton());
        return result;
    }

    private DistributionsPanel createDistributionsPanel() {
        return new DistributionsPanel(m_associations, this) {
            @Override
            protected EditWindow createEditor(final NamedObject object, final List<UIExtensionFactory> extensions) {
                return new EditWindow("Edit Distribution", object, extensions) {
                    @Override
                    protected void handleError(Exception e) {
                        getWindow().showNotification("Failed to edit distribution!",
                                "<br/>Reason: " + e.getMessage(), Notification.TYPE_ERROR_MESSAGE);
                    }

                    @Override
                    protected void onOk(String name, String description) throws Exception {
                        object.setDescription(description);
                    }
                };
            }

            @Override
            protected DistributionRepository getRepository() {
                return m_distributionRepository;
            }

            @Override
            protected RepositoryAdmin getRepositoryAdmin() {
                return m_admin;
            }
        };
    }

    private HorizontalLayout createDistributionToolbar() {
        HorizontalLayout result = new HorizontalLayout();
        result.setSpacing(true);
        result.addComponent(createAddDistributionButton());
        return result;
    }

    private FeaturesPanel createFeaturesPanel() {
        return new FeaturesPanel(m_associations, this) {
            @Override
            protected EditWindow createEditor(final NamedObject object, final List<UIExtensionFactory> extensions) {
                return new EditWindow("Edit Feature", object, extensions) {
                    @Override
                    protected void handleError(Exception e) {
                        getWindow().showNotification("Failed to edit feature!", "<br/>Reason: " + e.getMessage(),
                                Notification.TYPE_ERROR_MESSAGE);
                    }

                    @Override
                    protected void onOk(String name, String description) throws Exception {
                        object.setDescription(description);
                    }
                };
            }

            @Override
            protected FeatureRepository getRepository() {
                return m_featureRepository;
            }

            @Override
            protected RepositoryAdmin getRepositoryAdmin() {
                return m_admin;
            }
        };
    }

    private HorizontalLayout createFeatureToolbar() {
        HorizontalLayout result = new HorizontalLayout();
        result.setSpacing(true);
        result.addComponent(createAddFeatureButton());
        return result;
    }

    private Button createManageResourceProcessorsButton() {
        // Solves ACE-224
        Button button = new Button("RP");
        button.addListener(new Button.ClickListener() {
            @Override
            public void buttonClick(ClickEvent event) {
                showManageResourceProcessorsDialog();
            }
        });
        return button;
    }

    private Button createRegisterTargetsButton() {
        final Button button = new Button("R");
        button.setDisableOnClick(true);
        button.setImmediate(true);
        button.setEnabled(false);
        button.addListener(new Button.ClickListener() {
            @Override
            public void buttonClick(ClickEvent event) {
                m_targetsPanel.registerSelectedTargets();
            }
        });
        m_targetsPanel.addListener(new ValueChangeListener() {
            @Override
            public void valueChange(ValueChangeEvent event) {
                TargetsPanel targetsPanel = (TargetsPanel) event.getProperty();

                Collection<?> itemIDs = (Collection<?>) targetsPanel.getValue();

                boolean enabled = false;
                for (Object itemID : itemIDs) {
                    if (targetsPanel.isItemRegistrationNeeded(itemID)) {
                        enabled = true;
                        break;
                    }
                }

                button.setEnabled(enabled);
            }
        });
        return button;
    }

    private TargetsPanel createTargetsPanel() {
        return new TargetsPanel(m_associations, this) {
            @Override
            protected EditWindow createEditor(final NamedObject object, final List<UIExtensionFactory> extensions) {
                return new EditWindow("Edit Target", object, extensions) {
                    @Override
                    protected void handleError(Exception e) {
                        getWindow().showNotification("Failed to edit target!", "<br/>Reason: " + e.getMessage(),
                                Notification.TYPE_ERROR_MESSAGE);
                    }

                    @Override
                    protected void initDialog(NamedObject object, List<UIExtensionFactory> factories) {
                        m_name.setCaption("Identifier");
                        m_name.setReadOnly(true);
                        m_description.setVisible(false);

                        super.initDialog(object, factories);
                    }

                    @Override
                    protected void onOk(String name, String description) throws Exception {
                        // Nothing to edit!
                    }

                    @Override
                    protected Map<String, Object> populateContext(Map<String, Object> context) {
                        if (object instanceof NamedTargetObject) {
                            context.put("statefulTarget", m_statefulTargetRepository.get(object.getDefinition()));
                        } else if (object instanceof NamedStatefulTargetObject) {
                            context.put("statefulTarget", object.getObject());
                        }
                        return context;
                    }
                };
            }

            @Override
            protected TargetRepository getRepository() {
                return m_targetRepository;
            }

            @Override
            protected RepositoryAdmin getRepositoryAdmin() {
                return m_admin;
            }

            @Override
            protected StatefulTargetRepository getStatefulTargetRepository() {
                return m_statefulTargetRepository;
            }
        };
    }

    private HorizontalLayout createTargetToolbar() {
        HorizontalLayout result = new HorizontalLayout();
        result.setSpacing(true);
        result.addComponent(createAddTargetButton());
        result.addComponent(createRegisterTargetsButton());
        result.addComponent(createApproveTargetsButton());
        return result;
    }

    private GridLayout createToolbar() {
        return new MainActionToolbar(m_useAuth) {
            @Override
            protected void doAfterCommit() throws IOException {
                updateTableData();

                m_statusLine.setStatus("Local changes committed...");
            }

            @Override
            protected void doAfterLogout() throws IOException {
                // Close the application and reload the main window...
                close();
            }

            @Override
            protected void doAfterRetrieve() throws IOException {
                updateTableData();

                m_statusLine.setStatus("Repositories updated...");
            }

            @Override
            protected void doAfterRevert() throws IOException {
                updateTableData();

                m_statusLine.setStatus("Local changes reverted...");
            }

            @Override
            protected RepositoryAdmin getRepositoryAdmin() {
                return m_admin;
            }

            private void updateTableData() {
                m_artifactsPanel.populate();
                m_featuresPanel.populate();
                m_distributionsPanel.populate();
                m_targetsPanel.populate();

                m_mainWindow.focus();
            }
        };
    }

    /**
     * Authenticates the given user by creating all dependent services.
     * 
     * @param user
     * @throws IOException
     *             in case of I/O problems.
     */
    private boolean doLogin() {
        try {
            RepositoryAdminLoginContext context = m_admin.createLoginContext((User) getUser());

            // @formatter:off
            context.add(context.createShopRepositoryContext().setLocation(m_repository).setCustomer(customerName)
                    .setName(shopRepo).setWriteable())
                    .add(context.createTargetRepositoryContext().setLocation(m_repository).setCustomer(customerName)
                            .setName(targetRepo).setWriteable())
                    .add(context.createDeploymentRepositoryContext().setLocation(m_repository)
                            .setCustomer(customerName).setName(deployRepo).setWriteable());
            // @formatter:on

            m_admin.login(context);
            initGrid();
            m_admin.checkout();

            return true;
        } catch (Exception e) {
            m_log.log(LogService.LOG_WARNING, "Login failed! Destroying session...", e);

            try {
                // Avoid errors when the user tries to login again (due to the stale session)...
                m_admin.logout(true /* force */);
            } catch (IllegalStateException inner) {
                // Ignore; probably we're not logged...
            } catch (IOException inner) {
                m_log.log(LogService.LOG_WARNING, "Logout failed! Session possibly not destroyed...", inner);
            }

            return false;
        }
    }

    private void initGrid() {
        User user = (User) getUser();
        Authorization auth = m_userAdmin.getAuthorization(user);
        int count = 0;
        for (String role : new String[] { "viewArtifact", "viewFeature", "viewDistribution", "viewTarget" }) {
            if (auth.hasRole(role)) {
                count++;
            }
        }

        final GenericUploadHandler uploadHandler = new GenericUploadHandler(m_sessionDir) {
            @Override
            public void updateProgress(long readBytes, long contentLength) {
                Float percentage = new Float(readBytes / (float) contentLength);
                m_progress.setValue(percentage);
            }

            @Override
            protected void artifactsUploaded(List<UploadHandle> uploadedArtifacts) {
                StringBuilder failedMsg = new StringBuilder();
                StringBuilder successMsg = new StringBuilder();
                Set<String> selection = new HashSet<>();

                for (UploadHandle handle : uploadedArtifacts) {
                    if (!handle.isSuccessful()) {
                        // Upload failed, so let's report this one...
                        appendFailure(failedMsg, handle);

                        m_log.log(LogService.LOG_ERROR, "Upload of " + handle.getFile() + " failed.",
                                handle.getFailureReason());
                    } else {
                        try {
                            // Upload was successful, try to upload it to our OBR...
                            ArtifactObject artifact = uploadToOBR(handle);
                            if (artifact != null) {
                                selection.add(artifact.getDefinition());

                                appendSuccess(successMsg, handle);
                            }
                        } catch (ArtifactAlreadyExistsException exception) {
                            appendFailureExists(failedMsg, handle);

                            m_log.log(LogService.LOG_WARNING,
                                    "Upload of " + handle.getFilename() + " failed, as it already exists!");
                        } catch (Exception exception) {
                            appendFailure(failedMsg, handle, exception);

                            m_log.log(LogService.LOG_ERROR, "Upload of " + handle.getFilename() + " failed.",
                                    exception);
                        }
                    }

                    // We're done with this (temporary) file, so we can remove it...
                    handle.cleanup();
                }

                m_artifactsPanel.setValue(selection);

                // Notify the user what the overall status was...
                Notification notification = createNotification(failedMsg, successMsg);
                getMainWindow().showNotification(notification);

                m_progress.setStyleName("invisible");
                m_statusLine.setStatus(notification.getCaption() + "...");
            }

            @Override
            protected void uploadStarted(UploadHandle upload) {
                m_progress.setStyleName("visible");
                m_progress.setValue(new Float(0.0f));

                m_statusLine.setStatus("Upload of '%s' started...", upload.getFilename());
            }

            private void appendFailure(StringBuilder sb, UploadHandle handle) {
                appendFailure(sb, handle, handle.getFailureReason());
            }

            private void appendFailure(StringBuilder sb, UploadHandle handle, Exception cause) {
                sb.append("<li>'").append(handle.getFile().getName()).append("': failed");
                if (cause != null) {
                    sb.append(", possible reason:<br/>").append(cause.getMessage());
                }
                sb.append("</li>");
            }

            private void appendFailureExists(StringBuilder sb, UploadHandle handle) {
                sb.append("<li>'").append(handle.getFile().getName())
                        .append("': already exists in repository</li>");
            }

            private void appendSuccess(StringBuilder sb, UploadHandle handle) {
                sb.append("<li>'").append(handle.getFile().getName()).append("': added to repository</li>");
            }

            private Notification createNotification(StringBuilder failedMsg, StringBuilder successMsg) {
                String caption = "Upload completed";
                int delay = 500; // msec.
                StringBuilder notification = new StringBuilder();
                if (failedMsg.length() > 0) {
                    caption = "Upload completed with failures";
                    delay = -1;
                    notification.append("<ul>").append(failedMsg).append("</ul>");
                }
                if (successMsg.length() > 0) {
                    notification.append("<ul>").append(successMsg).append("</ul>");
                }
                if (delay < 0) {
                    notification.append("<p>(click to dismiss this notification).</p>");
                }

                Notification summary = new Notification(caption, notification.toString(),
                        Notification.TYPE_TRAY_NOTIFICATION);
                summary.setDelayMsec(delay);
                return summary;
            }

            private ArtifactObject uploadToOBR(UploadHandle handle) throws IOException {
                return UploadHelper.importRemoteBundle(m_artifactRepository, handle.getFile());
            }
        };

        m_grid = new GridLayout(count, 4);
        m_grid.setSpacing(true);
        m_grid.setSizeFull();

        m_mainToolbar = createToolbar();
        m_grid.addComponent(m_mainToolbar, 0, 0, count - 1, 0);

        m_artifactsPanel = createArtifactsPanel();
        m_artifactToolbar = createArtifactToolbar();

        final DragAndDropWrapper artifactsPanelWrapper = new DragAndDropWrapper(m_artifactsPanel);
        artifactsPanelWrapper.setDragStartMode(DragStartMode.HTML5);
        artifactsPanelWrapper.setDropHandler(new ArtifactDropHandler(uploadHandler));
        artifactsPanelWrapper.setCaption(m_artifactsPanel.getCaption());
        artifactsPanelWrapper.setSizeFull();

        count = 0;
        if (auth.hasRole("viewArtifact")) {
            m_grid.addComponent(artifactsPanelWrapper, count, 2);
            m_grid.addComponent(m_artifactToolbar, count, 1);
            count++;
        }

        m_featuresPanel = createFeaturesPanel();
        m_featureToolbar = createFeatureToolbar();

        if (auth.hasRole("viewFeature")) {
            m_grid.addComponent(m_featuresPanel, count, 2);
            m_grid.addComponent(m_featureToolbar, count, 1);
            count++;
        }

        m_distributionsPanel = createDistributionsPanel();
        m_distributionToolbar = createDistributionToolbar();

        if (auth.hasRole("viewDistribution")) {
            m_grid.addComponent(m_distributionsPanel, count, 2);
            m_grid.addComponent(m_distributionToolbar, count, 1);
            count++;
        }

        m_targetsPanel = createTargetsPanel();
        m_targetToolbar = createTargetToolbar();

        if (auth.hasRole("viewTarget")) {
            m_grid.addComponent(m_targetsPanel, count, 2);
            m_grid.addComponent(m_targetToolbar, count, 1);
        }

        m_statusLine = new StatusLine();

        m_grid.addComponent(m_statusLine, 0, 3, 2, 3);

        m_progress = new ProgressIndicator(0f);
        m_progress.setStyleName("invisible");
        m_progress.setIndeterminate(false);
        m_progress.setPollingInterval(1000);

        m_grid.addComponent(m_progress, 3, 3);

        m_grid.setRowExpandRatio(2, 1.0f);

        m_grid.setColumnExpandRatio(0, 0.31f);
        m_grid.setColumnExpandRatio(1, 0.23f);
        m_grid.setColumnExpandRatio(2, 0.23f);
        m_grid.setColumnExpandRatio(3, 0.23f);

        // Wire up all panels so they have the correct associations...
        m_artifactsPanel.setAssociatedTables(null, m_featuresPanel);
        m_featuresPanel.setAssociatedTables(m_artifactsPanel, m_distributionsPanel);
        m_distributionsPanel.setAssociatedTables(m_featuresPanel, m_targetsPanel);
        m_targetsPanel.setAssociatedTables(m_distributionsPanel, null);

        addListener(m_statusLine, StatefulTargetObject.TOPIC_ALL,
                RepositoryObject.PUBLIC_TOPIC_ROOT.concat(RepositoryObject.TOPIC_ALL_SUFFIX));
        addListener(m_mainToolbar, StatefulTargetObject.TOPIC_ALL, RepositoryAdmin.TOPIC_STATUSCHANGED,
                RepositoryAdmin.TOPIC_LOGIN, RepositoryAdmin.TOPIC_REFRESH);
        addListener(m_artifactsPanel, ArtifactObject.TOPIC_ALL, RepositoryAdmin.TOPIC_STATUSCHANGED,
                RepositoryAdmin.TOPIC_LOGIN, RepositoryAdmin.TOPIC_REFRESH);
        addListener(m_featuresPanel, FeatureObject.TOPIC_ALL, RepositoryAdmin.TOPIC_STATUSCHANGED,
                RepositoryAdmin.TOPIC_LOGIN, RepositoryAdmin.TOPIC_REFRESH);
        addListener(m_distributionsPanel, DistributionObject.TOPIC_ALL, RepositoryAdmin.TOPIC_STATUSCHANGED,
                RepositoryAdmin.TOPIC_LOGIN, RepositoryAdmin.TOPIC_REFRESH);
        addListener(m_targetsPanel, StatefulTargetObject.TOPIC_ALL, TargetObject.TOPIC_ALL,
                RepositoryAdmin.TOPIC_STATUSCHANGED, RepositoryAdmin.TOPIC_LOGIN, RepositoryAdmin.TOPIC_REFRESH);

        m_mainWindow.addComponent(m_grid);
        // Ensure the focus is properly defined (for the shortcut keys to work)...
        m_mainWindow.focus();
    }

    /**
     * @return <code>true</code> if the login succeeded, <code>false</code> otherwise.
     */
    private boolean loginAutomatically() {
        setUser(m_userAdmin.getUser("username", m_userName));
        return doLogin();
    }

    /**
     * Shows the login window on the center of the main window.
     */
    private void showLoginWindow() {
        LoginWindow loginWindow = new LoginWindow(m_log, this);

        loginWindow.openWindow(getMainWindow());
    }
}