ace-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ma...@apache.org
Subject svn commit: r1357570 [32/34] - in /ace/sandbox/marrs: cnf/ cnf/ext/ cnf/lib/ cnf/releaserepo/ cnf/repo/ cnf/repo/.obrcache/ cnf/repo/.obrcache/http%3A%2F%2Fbundles.bndtools.org.s3.amazonaws.com%2Fcom.jcraft.jsch/ cnf/repo/.obrcache/http%3A%2F%2Fbundles...
Date Thu, 05 Jul 2012 12:10:06 GMT
Added: ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/VaadinClient.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/VaadinClient.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/VaadinClient.java (added)
+++ ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/VaadinClient.java Thu Jul  5 12:09:30 2012
@@ -0,0 +1,989 @@
+/*
+ * 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.net.URLConnection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+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.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.stateful.StatefulTargetObject;
+import org.apache.ace.client.repository.stateful.StatefulTargetRepository;
+import org.apache.ace.connectionfactory.ConnectionFactory;
+import org.apache.ace.test.utils.FileUtils;
+import org.apache.ace.webui.NamedObject;
+import org.apache.ace.webui.UIExtensionFactory;
+import org.apache.ace.webui.vaadin.LoginWindow.LoginFunction;
+import org.apache.ace.webui.vaadin.component.ArtifactsPanel;
+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.TargetsPanel;
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.BundleContext;
+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.event.Transferable;
+import com.vaadin.event.dd.DragAndDropEvent;
+import com.vaadin.event.dd.DropHandler;
+import com.vaadin.event.dd.TargetDetails;
+import com.vaadin.event.dd.acceptcriteria.AcceptCriterion;
+import com.vaadin.event.dd.acceptcriteria.Or;
+import com.vaadin.ui.AbstractSelect.AbstractSelectTargetDetails;
+import com.vaadin.ui.AbstractSelect.VerticalLocationIs;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Button.ClickEvent;
+import com.vaadin.ui.CheckBox;
+import com.vaadin.ui.GridLayout;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.ProgressIndicator;
+import com.vaadin.ui.Table;
+import com.vaadin.ui.Table.TableTransferable;
+import com.vaadin.ui.Window;
+
+/*
+
+ TODO:
+ - Add buttons to remove associations (think about how we can better visualize this)
+ - Add buttons to remove objects
+ - Handle ui updates better
+ - Add functionality for adding an artifact
+ - Allow live updates of the target column
+ - Create a special editor for dealing with new artifact types
+
+ - Enable drag and drop to create associations (done)
+ - Add drag and drop to the artifacts column (done)
+ - Add an editor that appears on double clicking on an item in a table (done)
+ - Add buttons to create new items in all of the tables (done for those that make sense)
+ */
+@SuppressWarnings("serial")
+public class VaadinClient extends com.vaadin.Application implements AssociationRemover, LoginFunction {
+
+    private static final long serialVersionUID = 1L;
+
+    private static long SESSION_ID = 12345;
+
+    private static String targetRepo = "target";
+    private static String shopRepo = "shop";
+    private static String deployRepo = "deployment";
+    private static String customerName = "apache";
+    private static String endpoint = "/repository";
+
+    private volatile AuthenticationService m_authenticationService;
+    private volatile DependencyManager m_manager;
+    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 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 boolean m_dynamicRelations = true;
+    private File m_sessionDir; // private folder for session info
+    private HorizontalLayout m_artifactToolbar;
+    private Button m_featureToolbar;
+    private Button m_distributionToolbar;
+    private Button m_targetToolbar;
+    private Window m_mainWindow;
+
+    private final URL m_obrUrl;
+    private final URL m_repository;
+    private final boolean m_useAuth;
+    private final String m_userName;
+
+    private final Associations m_associations = new Associations();
+    private final AtomicBoolean m_dependenciesResolved = new AtomicBoolean(false);
+
+    private ProgressIndicator m_progress;
+
+    // basic session ID generator
+    private static long generateSessionID() {
+        return SESSION_ID++;
+    }
+
+    /**
+     * Creates a new {@link VaadinClient} instance.
+     * 
+     * @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(URL aceHost, URL obrUrl, boolean useAuth, String userName) {
+        try {
+            m_repository = new URL(aceHost, endpoint);
+        }
+        catch (MalformedURLException e) {
+            throw new IllegalArgumentException("Need a valid repository URL!", e);
+        }
+        m_obrUrl = obrUrl;
+        m_useAuth = useAuth;
+        m_userName = userName;
+
+        if (!m_useAuth && (m_userName == null || "".equals(m_userName.trim()))) {
+            throw new IllegalArgumentException("Need a valid user name when no authentication is used!");
+        }
+    }
+
+    public void setupDependencies(Component component) {
+        m_sessionID = "" + generateSessionID();
+        File dir = m_context.getDataFile(m_sessionID);
+        dir.mkdir();
+        m_sessionDir = dir;
+        m_sessionFactory.createSession(m_sessionID);
+        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, StatefulTargetRepository.class);
+        addDependency(component, ConnectionFactory.class);
+    }
+
+    public void start() {
+        m_log.log(LogService.LOG_INFO, "Starting session #" + m_sessionID);
+        m_dependenciesResolved.set(true);
+    }
+
+    public void stop() {
+        m_log.log(LogService.LOG_INFO, "Stopping session #" + m_sessionID);
+        m_dependenciesResolved.set(false);
+    }
+
+    public void destroyDependencies() {
+        m_sessionFactory.destroySession(m_sessionID);
+        FileUtils.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();
+
+        setMainWindow(m_mainWindow);
+
+        // Authenticate the user either by showing a login window; or by another means...
+        authenticate();
+    }
+
+    /**
+     * Shows the login window on the center of the main window.
+     */
+    private void showLoginWindow() {
+        LoginWindow loginWindow = new LoginWindow(m_log, this);
+
+        m_mainWindow.addWindow(loginWindow);
+
+        loginWindow.center();
+    }
+
+    private void initGrid(User user) {
+        Authorization auth = m_userAdmin.getAuthorization(user);
+        int count = 0;
+        for (String role : new String[] { "viewArtifact", "viewFeature", "viewDistribution", "viewTarget" }) {
+            if (auth.hasRole(role)) {
+                count++;
+            }
+        }
+        m_grid = new GridLayout(count, 4);
+        m_grid.setSpacing(true);
+        m_grid.setSizeFull();
+
+        m_grid.addComponent(createToolbar(), 0, 0, count - 1, 0);
+
+        m_artifactsPanel = createArtifactsPanel();
+
+        m_artifactToolbar = new HorizontalLayout();
+        m_artifactToolbar.addComponent(createAddArtifactButton());
+
+        CheckBox dynamicCheckBox = new CheckBox("Dynamic Links");
+        dynamicCheckBox.setImmediate(true);
+        dynamicCheckBox.setValue(Boolean.TRUE);
+        dynamicCheckBox.addListener(new Button.ClickListener() {
+            public void buttonClick(ClickEvent event) {
+                m_dynamicRelations = event.getButton().booleanValue();
+            }
+        });
+        m_artifactToolbar.addComponent(dynamicCheckBox);
+
+        count = 0;
+        if (auth.hasRole("viewArtifact")) {
+            m_grid.addComponent(m_artifactsPanel, count, 2);
+            m_grid.addComponent(m_artifactToolbar, count, 1);
+            count++;
+        }
+
+        m_featuresPanel = createFeaturesPanel();
+        m_featureToolbar = createAddFeatureButton();
+
+        if (auth.hasRole("viewFeature")) {
+            m_grid.addComponent(m_featuresPanel, count, 2);
+            m_grid.addComponent(m_featureToolbar, count, 1);
+            count++;
+        }
+
+        m_distributionsPanel = createDistributionsPanel();
+        m_distributionToolbar = createAddDistributionButton();
+
+        if (auth.hasRole("viewDistribution")) {
+            m_grid.addComponent(m_distributionsPanel, count, 2);
+            m_grid.addComponent(m_distributionToolbar, count, 1);
+            count++;
+        }
+
+        m_targetsPanel = createTargetsPanel();
+        m_targetToolbar = createAddTargetButton();
+
+        if (auth.hasRole("viewTarget")) {
+            m_grid.addComponent(m_targetsPanel, count, 2);
+            m_grid.addComponent(m_targetToolbar, count, 1);
+        }
+
+        // Wire up all panels so they have the correct associations...
+        m_artifactsPanel.setLeftTable(null);
+        m_artifactsPanel.setRightTable(m_featuresPanel);
+
+        m_featuresPanel.setLeftTable(m_artifactsPanel);
+        m_featuresPanel.setRightTable(m_distributionsPanel);
+
+        m_distributionsPanel.setLeftTable(m_featuresPanel);
+        m_distributionsPanel.setRightTable(m_targetsPanel);
+
+        m_targetsPanel.setLeftTable(m_distributionsPanel);
+        m_targetsPanel.setRightTable(null);
+
+        m_grid.setRowExpandRatio(2, 1.0f);
+
+        m_progress = new ProgressIndicator(0f);
+        m_progress.setStyleName("invisible");
+        m_progress.setPollingInterval(500);
+
+        m_grid.addComponent(m_progress, 0, 3);
+
+        m_artifactsPanel.addListener(m_associations.createSelectionListener(m_artifactsPanel, m_artifactRepository,
+            new Class[] {}, new Class[] { FeatureObject.class, DistributionObject.class, TargetObject.class },
+            new Table[] { m_featuresPanel, m_distributionsPanel, m_targetsPanel }));
+        m_featuresPanel.addListener(m_associations.createSelectionListener(m_featuresPanel, m_featureRepository,
+            new Class[] { ArtifactObject.class }, new Class[] { DistributionObject.class, TargetObject.class },
+            new Table[] { m_artifactsPanel, m_distributionsPanel, m_targetsPanel }));
+        m_distributionsPanel.addListener(m_associations.createSelectionListener(m_distributionsPanel,
+            m_distributionRepository,
+            new Class[] { FeatureObject.class, ArtifactObject.class }, new Class[] { TargetObject.class },
+            new Table[] { m_artifactsPanel, m_featuresPanel, m_targetsPanel }));
+        m_targetsPanel.addListener(m_associations.createSelectionListener(m_targetsPanel, m_statefulTargetRepository,
+            new Class[] { DistributionObject.class, FeatureObject.class, ArtifactObject.class }, new Class[] {},
+            new Table[] { m_artifactsPanel, m_featuresPanel, m_distributionsPanel }));
+
+        m_artifactsPanel.setDropHandler(new AssociationDropHandler((Table) null, m_featuresPanel) {
+            @Override
+            protected void associateFromLeft(String left, String right) {
+            }
+
+            @Override
+            protected void associateFromRight(String left, String right) {
+                ArtifactObject artifact = getArtifact(left);
+                // 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
+                if (artifact != null) {
+                    if (m_dynamicRelations) {
+                        Map<String, String> properties = new HashMap<String, String>();
+                        properties.put(BundleHelper.KEY_ASSOCIATION_VERSIONSTATEMENT, "0.0.0");
+                        m_artifact2featureAssociationRepository.create(artifact, properties, getFeature(right), null);
+                    }
+                    else {
+                        m_artifact2featureAssociationRepository.create(artifact, getFeature(right));
+                    }
+                }
+            }
+        });
+        m_featuresPanel.setDropHandler(new AssociationDropHandler(m_artifactsPanel, m_distributionsPanel) {
+            @Override
+            protected void associateFromLeft(String left, String right) {
+                ArtifactObject artifact = getArtifact(left);
+                // 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
+                if (artifact != null) {
+                    if (m_dynamicRelations) {
+                        Map<String, String> properties = new HashMap<String, String>();
+                        properties.put(BundleHelper.KEY_ASSOCIATION_VERSIONSTATEMENT, "0.0.0");
+                        m_artifact2featureAssociationRepository.create(artifact, properties, getFeature(right), null);
+                    }
+                    else {
+                        m_artifact2featureAssociationRepository.create(artifact, getFeature(right));
+                    }
+                }
+            }
+
+            @Override
+            protected void associateFromRight(String left, String right) {
+                m_feature2distributionAssociationRepository.create(getFeature(left), getDistribution(right));
+            }
+        });
+        m_distributionsPanel.setDropHandler(new AssociationDropHandler(m_featuresPanel, m_targetsPanel) {
+            @Override
+            protected void associateFromLeft(String left, String right) {
+                m_feature2distributionAssociationRepository.create(getFeature(left), getDistribution(right));
+            }
+
+            @Override
+            protected void associateFromRight(String left, String right) {
+                StatefulTargetObject target = getTarget(right);
+                if (!target.isRegistered()) {
+                    target.register();
+                    target.setAutoApprove(true);
+                }
+                m_distribution2targetAssociationRepository.create(getDistribution(left), target.getTargetObject());
+            }
+        });
+        m_targetsPanel.setDropHandler(new AssociationDropHandler(m_distributionsPanel, (Table) null) {
+            @Override
+            protected void associateFromLeft(String left, String right) {
+                StatefulTargetObject target = getTarget(right);
+                if (!target.isRegistered()) {
+                    target.register();
+                    target.setAutoApprove(true);
+                }
+                m_distribution2targetAssociationRepository.create(getDistribution(left), target.getTargetObject());
+            }
+
+            @Override
+            protected void associateFromRight(String left, String right) {
+            }
+        });
+
+        addListener(m_artifactsPanel, ArtifactObject.TOPIC_ALL, RepositoryAdmin.TOPIC_STATUSCHANGED);
+        addListener(m_featuresPanel, FeatureObject.TOPIC_ALL, RepositoryAdmin.TOPIC_STATUSCHANGED);
+        addListener(m_distributionsPanel, DistributionObject.TOPIC_ALL, RepositoryAdmin.TOPIC_STATUSCHANGED);
+        addListener(m_targetsPanel, StatefulTargetObject.TOPIC_ALL, TargetObject.TOPIC_ALL,
+            RepositoryAdmin.TOPIC_STATUSCHANGED);
+
+        m_mainWindow.addComponent(m_grid);
+    }
+
+    /**
+     * {@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);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean login(String username, String password) {
+        User user = m_authenticationService.authenticate(username, password);
+        setUser(user);
+        return login(user);
+    }
+
+    private void addSessionDependency(Component component, Class service) {
+        component.add(m_manager.createServiceDependency()
+            .setService(service, "(" + SessionFactory.SERVICE_SID + "=" + m_sessionID + ")")
+            .setRequired(true)
+            .setInstanceBound(true));
+    }
+
+    private void addDependency(Component component, Class service) {
+        component.add(m_manager.createServiceDependency()
+            .setService(service)
+            .setRequired(true)
+            .setInstanceBound(true));
+    }
+
+    /**
+     * @return <code>true</code> if the login succeeded, <code>false</code> otherwise.
+     */
+    private boolean loginAutomatically() {
+        User user = m_userAdmin.getUser("username", m_userName);
+        setUser(user);
+        return login(user);
+    }
+
+    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 + ")");
+        // @formatter:off
+        m_manager.add(
+            m_manager.createComponent()
+                .setInterface(EventHandler.class.getName(), props)
+                .setImplementation(implementation));
+        // @formatter:on
+    }
+
+    /**
+     * Determines how authentication should take place.
+     */
+    private void authenticate() {
+        if (m_useAuth) {
+            showLoginWindow();
+        }
+        else {
+            // Not using authentication; use fallback scenario...
+            loginAutomatically();
+        }
+    }
+
+    private GridLayout createToolbar() {
+        final boolean showLogoutButton = m_useAuth;
+        MainActionToolbar mainActionToolbar = new MainActionToolbar(showLogoutButton) {
+            @Override
+            protected RepositoryAdmin getRepositoryAdmin() {
+                return m_admin;
+            }
+
+            @Override
+            protected void doAfterRevert() throws IOException {
+                updateTableData();
+            }
+
+            @Override
+            protected void doAfterRetrieve() throws IOException {
+                updateTableData();
+            }
+
+            @Override
+            protected void doAfterCommit() throws IOException {
+                updateTableData();
+            }
+
+            @Override
+            protected void doAfterLogout() throws IOException {
+                // Close the application and reload the main window...
+                close();
+            }
+
+            private void updateTableData() {
+                m_artifactsPanel.populate();
+                m_featuresPanel.populate();
+                m_distributionsPanel.populate();
+                m_targetsPanel.populate();
+            }
+        };
+        addListener(mainActionToolbar, RepositoryObject.PUBLIC_TOPIC_ROOT.concat(RepositoryObject.TOPIC_ALL_SUFFIX));
+        return mainActionToolbar;
+    }
+
+    private ArtifactsPanel createArtifactsPanel() {
+        return new ArtifactsPanel(m_associations, this) {
+            @Override
+            protected EditWindow createEditor(final NamedObject object, final List<UIExtensionFactory> extensions) {
+                return new EditWindow("Edit Artifact", object, extensions) {
+                    @Override
+                    protected void onOk(String name, String description) throws Exception {
+                        object.setDescription(description);
+                    }
+
+                    @Override
+                    protected void handleError(Exception e) {
+                        getWindow().showNotification("Failed to edit artifact!", "<br/>Reason: " + e.getMessage(),
+                            Notification.TYPE_ERROR_MESSAGE);
+                    }
+                };
+            }
+
+            @Override
+            protected ArtifactRepository getRepository() {
+                return m_artifactRepository;
+            }
+
+            @Override
+            protected RepositoryAdmin getRepositoryAdmin() {
+                return m_admin;
+            }
+        };
+    }
+
+    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 onOk(String name, String description) throws Exception {
+                        object.setDescription(description);
+                    }
+
+                    @Override
+                    protected void handleError(Exception e) {
+                        getWindow().showNotification("Failed to edit feature!", "<br/>Reason: " + e.getMessage(),
+                            Notification.TYPE_ERROR_MESSAGE);
+                    }
+                };
+            }
+
+            @Override
+            protected FeatureRepository getRepository() {
+                return m_featureRepository;
+            }
+
+            @Override
+            protected RepositoryAdmin getRepositoryAdmin() {
+                return m_admin;
+            }
+        };
+    }
+
+    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 onOk(String name, String description) throws Exception {
+                        object.setDescription(description);
+                    }
+
+                    @Override
+                    protected void handleError(Exception e) {
+                        getWindow().showNotification("Failed to edit distribution!", "<br/>Reason: " + e.getMessage(),
+                            Notification.TYPE_ERROR_MESSAGE);
+                    }
+                };
+            }
+
+            @Override
+            protected DistributionRepository getRepository() {
+                return m_distributionRepository;
+            }
+
+            @Override
+            protected RepositoryAdmin getRepositoryAdmin() {
+                return m_admin;
+            }
+        };
+    }
+
+    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 onOk(String name, String description) throws Exception {
+                        // Nothing to edit!
+                    }
+
+                    @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 StatefulTargetRepository getRepository() {
+                return m_statefulTargetRepository;
+            }
+
+            @Override
+            protected RepositoryAdmin getRepositoryAdmin() {
+                return m_admin;
+            }
+        };
+    }
+
+    private abstract class AssociationDropHandler implements DropHandler {
+        private final Table m_left;
+        private final Table m_right;
+
+        public AssociationDropHandler(Table left, Table right) {
+            m_left = left;
+            m_right = right;
+        }
+
+        public void drop(DragAndDropEvent event) {
+            Transferable transferable = event.getTransferable();
+            TargetDetails targetDetails = event.getTargetDetails();
+            if (transferable instanceof TableTransferable) {
+                TableTransferable tt = (TableTransferable) transferable;
+                Object fromItemId = tt.getItemId();
+                // get the active selection, but only if we drag from the same table
+                Set<?> selection =
+                    m_associations.isActiveTable(tt.getSourceComponent()) ? m_associations.getActiveSelection() : null;
+                if (targetDetails instanceof AbstractSelectTargetDetails) {
+                    AbstractSelectTargetDetails ttd = (AbstractSelectTargetDetails) targetDetails;
+                    Object toItemId = ttd.getItemIdOver();
+                    if (tt.getSourceComponent().equals(m_left)) {
+                        if (selection != null) {
+                            for (Object item : selection) {
+                                associateFromLeft((String) item, (String) toItemId);
+                            }
+                        }
+                        else {
+                            associateFromLeft((String) fromItemId, (String) toItemId);
+                        }
+                    }
+                    else if (tt.getSourceComponent().equals(m_right)) {
+                        if (selection != null) {
+                            for (Object item : selection) {
+                                associateFromRight((String) toItemId, (String) item);
+                            }
+                        }
+                        else {
+                            associateFromRight((String) toItemId, (String) fromItemId);
+                        }
+                    }
+                    // TODO add to highlighting (it's probably easiest to
+                    // recalculate the whole set of related and associated
+                    // items here, see SelectionListener, or to manually figure
+                    // out the changes in all cases
+                }
+            }
+        }
+
+        public AcceptCriterion getAcceptCriterion() {
+            return new Or(VerticalLocationIs.MIDDLE);
+        }
+
+        protected abstract void associateFromLeft(String left, String right);
+
+        protected abstract void associateFromRight(String left, String right);
+    }
+
+    /**
+     * Create a button to show a pop window for adding new features.
+     * 
+     * @param main Main Window
+     * @return Button
+     */
+    private Button createAddArtifactButton() {
+        Button button = new Button("Add artifact...");
+        button.addListener(new Button.ClickListener() {
+            public void buttonClick(ClickEvent event) {
+                showAddArtifactDialog();
+            }
+        });
+        return button;
+    }
+
+    /***
+     * Create a button to show popup window for adding a new feature. On success
+     * this calls the createFeature() method.
+     * 
+     * @return the add-feature button instance.
+     */
+    private Button createAddFeatureButton() {
+        Button button = new Button("Add Feature...");
+        button.addListener(new Button.ClickListener() {
+            public void buttonClick(ClickEvent event) {
+                GenericAddWindow window = new GenericAddWindow("Add Feature") {
+                    public void onOk(String name, String description) {
+                        createFeature(name, description);
+                    }
+
+                    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);
+                    }
+                };
+                window.show(getMainWindow());
+            }
+        });
+        return button;
+    }
+
+    /**
+     * Create a button to show a popup window for adding a new distribution. On
+     * success this calls the createDistribution() method.
+     * 
+     * @return the add-distribution button instance.
+     */
+    private Button createAddDistributionButton() {
+        Button button = new Button("Add Distribution...");
+        button.addListener(new Button.ClickListener() {
+            public void buttonClick(ClickEvent event) {
+                GenericAddWindow window = new GenericAddWindow("Add Distribution") {
+                    public void onOk(String name, String description) {
+                        createDistribution(name, description);
+                    }
+
+                    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);
+                    }
+                };
+                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
+     * 
+     * @return the add-target button instance.
+     */
+    private Button createAddTargetButton() {
+        Button button = new Button("Add target...");
+        button.addListener(new Button.ClickListener() {
+            public void buttonClick(ClickEvent event) {
+                GenericAddWindow window = new GenericAddWindow("Add Target") {
+                    protected void onOk(String id, String description) {
+                        createTarget(id);
+                    }
+
+                    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();
+                    }
+                };
+                window.show(getMainWindow());
+            }
+        });
+        return button;
+    }
+
+    /**
+     * Create a new feature in the feature repository.
+     * 
+     * @param name the name of the new feature;
+     * @param description the description of the new feature.
+     */
+    private void createFeature(String name, String description) {
+        Map<String, String> attributes = new HashMap<String, String>();
+        attributes.put(FeatureObject.KEY_NAME, name);
+        attributes.put(FeatureObject.KEY_DESCRIPTION, description);
+        Map<String, String> tags = new HashMap<String, String>();
+        m_featureRepository.create(attributes, tags);
+    }
+
+    /**
+     * Create a new target in the stateful target repository.
+     * 
+     * @param name the name of the new target;
+     */
+    private void createTarget(String name) {
+        Map<String, String> attributes = new HashMap<String, String>();
+        attributes.put(StatefulTargetObject.KEY_ID, name);
+        attributes.put(TargetObject.KEY_AUTO_APPROVE, "true");
+        Map<String, String> tags = new HashMap<String, String>();
+        m_statefulTargetRepository.preregister(attributes, tags);
+    }
+
+    /**
+     * Create a new distribution in the distribution repository
+     * 
+     * @param name the name of the new distribution;
+     * @param description the description of the new distribution.
+     */
+    private void createDistribution(String name, String description) {
+        Map<String, String> attributes = new HashMap<String, String>();
+        attributes.put(DistributionObject.KEY_NAME, name);
+        attributes.put(DistributionObject.KEY_DESCRIPTION, description);
+        Map<String, String> tags = new HashMap<String, String>();
+        m_distributionRepository.create(attributes, tags);
+    }
+
+    private ArtifactObject getArtifact(String definition) {
+        return m_artifactRepository.get(definition);
+    }
+
+    private FeatureObject getFeature(String name) {
+        return m_featureRepository.get(name);
+    }
+
+    private DistributionObject getDistribution(String name) {
+        return m_distributionRepository.get(name);
+    }
+
+    private StatefulTargetObject getTarget(String name) {
+        return m_statefulTargetRepository.get(name);
+    }
+
+    @Override
+    public void close() {
+        super.close();
+        // when the session times out
+        // TODO: clean up the ace client session?
+    }
+
+    private void showAddArtifactDialog() {
+        final AddArtifactWindow window = new AddArtifactWindow(m_sessionDir, m_obrUrl) {
+            @Override
+            protected ArtifactRepository getArtifactRepository() {
+                return m_artifactRepository;
+            }
+            
+            @Override
+            protected URLConnection openConnection(URL url) throws IOException {
+                return m_connectionFactory.createConnection(url);
+            }
+
+            @Override
+            protected LogService getLogger() {
+                return m_log;
+            }
+        };
+
+        // Open the subwindow by adding it to the parent window
+        window.showWindow(getMainWindow());
+    }
+
+    /**
+     * Authenticates the given user by creating all dependent services.
+     * 
+     * @param user
+     * @throws IOException in case of I/O problems.
+     */
+    private boolean login(final User user) {
+        try {
+            RepositoryAdminLoginContext context = m_admin.createLoginContext(user);
+            
+            // @formatter:off
+            context.setObrBase(m_obrUrl)
+                .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(user);
+            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;
+        }
+    }
+}

Added: ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/VaadinResourceHandler.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/VaadinResourceHandler.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/VaadinResourceHandler.java (added)
+++ ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/VaadinResourceHandler.java Thu Jul  5 12:09:30 2012
@@ -0,0 +1,81 @@
+/*
+ * 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.IOException;
+import java.net.URL;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.service.http.HttpContext;
+import org.osgi.service.http.HttpService;
+import org.osgi.service.http.NamespaceException;
+
+public class VaadinResourceHandler {
+    private volatile HttpService m_http;
+    private HttpContext m_context;
+	private BundleContext m_bundleContext;
+    
+    public void start() {
+        m_context = m_http.createDefaultHttpContext();
+        try {
+            m_http.registerResources("/VAADIN", "/VAADIN", new HttpContext() {
+                public String getMimeType(String name) {
+                    return m_context.getMimeType(name);
+                }
+
+                /**
+                 * ACE uses a slightly modified version of the 'reindeer' theme. To avoid having
+                 * to copy all resources in the Vaadin jar, we only override the files we changed
+                 * and do replace the theme name 'ace' with 'reindeer' before we go looking for the
+                 * original files.
+                 * 
+                 * When updating to a new Vaadin version, usually you need to copy the styles.css
+                 * file from the original archive again and append the ACE changes to the end, as this
+                 * file tends to change considerably between versions.
+                 */
+                public URL getResource(String name) {
+                    URL resource = null;
+                    String prefix = "/VAADIN/";
+                    // fix for ACE-156
+                    if (!name.startsWith("/")) {
+                        name = "/" + name;
+                    }
+					if (name.startsWith(prefix)) {
+                    	String originalName = name.replace("/ace/", "/reindeer/");
+                        resource = m_bundleContext.getBundle().getEntry(originalName);
+                        if (resource == null) {
+                            // try to find the resource in the Vaadin bundle instead
+                            resource = com.vaadin.Application.class.getResource(originalName);
+                        }
+                    }
+                    return resource;
+                }
+
+                public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException {
+                    return m_context.handleSecurity(request, response);
+                }});
+        }
+        catch (NamespaceException e) {
+            e.printStackTrace();
+        }
+    }
+}

Added: ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/VaadinServlet.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/VaadinServlet.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/VaadinServlet.java (added)
+++ ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/VaadinServlet.java Thu Jul  5 12:09:30 2012
@@ -0,0 +1,135 @@
+/*
+ * 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.net.MalformedURLException;
+import java.net.URL;
+import java.util.Dictionary;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.ace.authentication.api.AuthenticationService;
+import org.apache.ace.client.repository.SessionFactory;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.log.LogService;
+import org.osgi.service.useradmin.UserAdmin;
+
+import com.vaadin.Application;
+import com.vaadin.terminal.gwt.server.AbstractApplicationServlet;
+
+public class VaadinServlet extends AbstractApplicationServlet implements ManagedService {
+    private static final long serialVersionUID = 1L;
+    
+    public static final String PID = "org.apache.ace.webui.vaadin";
+    
+    /** A boolean denoting whether or not authentication is enabled. */
+    private static final String KEY_USE_AUTHENTICATION = "ui.authentication.enabled";
+    /** Name of the user to log in as. */
+    private static final String KEY_USER_NAME = "ui.authentication.user.name";
+    /** A string denoting the host name of the management service. */
+    private static final String KEY_ACE_HOST = "ace.host";
+    /** A string denoting the URL to the management server's OBR. */
+    private static final String KEY_OBR_URL = "obr.url";
+
+    private volatile DependencyManager m_manager;
+
+    private volatile boolean m_useAuth;
+    private volatile String m_userName;
+    private volatile URL m_aceHost;
+    private volatile URL m_obrUrl;
+    
+    @Override
+    protected Class<? extends Application> getApplicationClass() {
+        return VaadinClient.class;
+    }
+
+    @Override
+    protected Application getNewApplication(HttpServletRequest request)	throws ServletException {
+        Application application = new VaadinClient(m_aceHost, m_obrUrl, m_useAuth, m_userName);
+        m_manager.add(m_manager.createComponent()
+            .setImplementation(application)
+            .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)
+            )
+        );
+        return application;
+    }
+
+    public void updated(Dictionary dictionary) throws ConfigurationException {
+        if (dictionary != null) {
+            URL aceHost;
+            try {
+                String aceHostString = (String) dictionary.get(KEY_ACE_HOST);
+                if (aceHostString == null) {
+                    throw new ConfigurationException(KEY_ACE_HOST, "Missing property");
+                }
+                aceHost = new URL(aceHostString);
+            }
+            catch (MalformedURLException e) {
+                throw new ConfigurationException(KEY_ACE_HOST, "Is not a valid URL", e);
+            }
+
+            URL obrUrl;
+            try {
+                String obrUrlString = (String) dictionary.get(KEY_OBR_URL);
+                if (obrUrlString == null) {
+                    throw new ConfigurationException(KEY_OBR_URL, "Missing property");
+                }
+                obrUrl = new URL(obrUrlString);
+            }
+            catch (MalformedURLException e) {
+                throw new ConfigurationException(KEY_OBR_URL, "Is not a valid URL", e);
+            }
+
+            String useAuthString = (String) dictionary.get(KEY_USE_AUTHENTICATION);
+            if (useAuthString == null || !("true".equalsIgnoreCase(useAuthString) || "false".equalsIgnoreCase(useAuthString))) {
+                throw new ConfigurationException(KEY_USE_AUTHENTICATION, "Missing or invalid value!");
+            }
+            boolean useAuth = Boolean.parseBoolean(useAuthString);
+
+            String userNameString = (String) dictionary.get(KEY_USER_NAME);
+            if ((userNameString == null) && !useAuth) {
+                throw new ConfigurationException(KEY_USER_NAME, "Missing value; authentication is disabled!");
+            }
+
+            m_useAuth = useAuth;
+            m_userName = userNameString;
+            m_aceHost = aceHost;
+            m_obrUrl = obrUrl;
+        }
+    }
+
+}

Added: ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/component/ArtifactsPanel.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/component/ArtifactsPanel.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/component/ArtifactsPanel.java (added)
+++ ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/component/ArtifactsPanel.java Thu Jul  5 12:09:30 2012
@@ -0,0 +1,107 @@
+/*
+ * 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.component;
+
+import java.util.List;
+
+import org.apache.ace.client.repository.RepositoryAdmin;
+import org.apache.ace.client.repository.RepositoryObject;
+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.FeatureObject;
+import org.apache.ace.client.repository.repository.ArtifactRepository;
+import org.apache.ace.webui.UIExtensionFactory;
+import org.apache.ace.webui.vaadin.AssociationRemover;
+import org.apache.ace.webui.vaadin.Associations;
+
+import com.vaadin.data.Item;
+
+/**
+ * Provides an object panel for displaying artifacts.
+ */
+public abstract class ArtifactsPanel extends BaseObjectPanel<ArtifactObject, ArtifactRepository> {
+
+    /**
+     * Creates a new {@link ArtifactsPanel} instance.
+     * 
+     * @param associations the assocation-holder object;
+     * @param associationRemover the helper for removing associations.
+     */
+    public ArtifactsPanel(Associations associations, AssociationRemover associationRemover) {
+        super(associations, associationRemover, "Artifact", UIExtensionFactory.EXTENSION_POINT_VALUE_ARTIFACT, true);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected boolean doRemoveRightSideAssociation(ArtifactObject object, RepositoryObject other) {
+        List<Artifact2FeatureAssociation> associations = object.getAssociationsWith((FeatureObject) other);
+        for (Artifact2FeatureAssociation association : associations) {
+            m_associationRemover.removeAssociation(association);
+        }
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void handleEvent(String topic, RepositoryObject entity, org.osgi.service.event.Event event) {
+        ArtifactObject artifact = (ArtifactObject) entity;
+        if (ArtifactObject.TOPIC_ADDED.equals(topic)) {
+            add(artifact);
+        }
+        if (ArtifactObject.TOPIC_REMOVED.equals(topic)) {
+            remove(artifact);
+        }
+        if (ArtifactObject.TOPIC_CHANGED.equals(topic) || RepositoryAdmin.TOPIC_STATUSCHANGED.equals(topic)) {
+            update(artifact);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected boolean isSupportedEntity(RepositoryObject entity) {
+        return (entity instanceof ArtifactObject) && !isResourceProcessor((ArtifactObject) entity);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void populateItem(ArtifactObject artifact, Item item) {
+        item.getItemProperty(WORKING_STATE_ICON).setValue(getWorkingStateIcon(artifact));
+        item.getItemProperty(OBJECT_NAME).setValue(artifact.getName());
+        item.getItemProperty(OBJECT_DESCRIPTION).setValue(artifact.getDescription());
+        item.getItemProperty(ACTIONS).setValue(createActionButtons(artifact));
+    }
+
+    /**
+     * Returns whether or not the given artifact is actually a resource processor.
+     * 
+     * @param artifact the artifact to test, cannot be <code>null</code>.
+     * @return <code>true</code> if the given artifact is a resource processor, <code>false</code> otherwise.
+     */
+    private boolean isResourceProcessor(ArtifactObject artifact) {
+        return artifact.getAttribute(BundleHelper.KEY_RESOURCE_PROCESSOR_PID) != null;
+    }
+}

Added: ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/component/BaseObjectPanel.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/component/BaseObjectPanel.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/component/BaseObjectPanel.java (added)
+++ ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/component/BaseObjectPanel.java Thu Jul  5 12:09:30 2012
@@ -0,0 +1,590 @@
+/*
+ * 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.component;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.ace.client.repository.ObjectRepository;
+import org.apache.ace.client.repository.RepositoryAdmin;
+import org.apache.ace.client.repository.RepositoryObject;
+import org.apache.ace.client.repository.RepositoryObject.WorkingState;
+import org.apache.ace.webui.NamedObject;
+import org.apache.ace.webui.UIExtensionFactory;
+import org.apache.ace.webui.vaadin.AssociationRemover;
+import org.apache.ace.webui.vaadin.Associations;
+import org.apache.ace.webui.vaadin.EditWindow;
+import org.apache.felix.dm.Component;
+import org.apache.felix.dm.DependencyManager;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.event.EventHandler;
+
+import com.vaadin.data.Item;
+import com.vaadin.event.ItemClickEvent;
+import com.vaadin.event.ItemClickEvent.ItemClickListener;
+import com.vaadin.terminal.Resource;
+import com.vaadin.terminal.ThemeResource;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Embedded;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.Table;
+import com.vaadin.ui.Window.Notification;
+
+/**
+ * Provides a custom table for displaying artifacts, features and so on.
+ */
+abstract class BaseObjectPanel<REPO_OBJ extends RepositoryObject, REPO extends ObjectRepository<REPO_OBJ>> extends Table implements EventHandler {
+
+    /**
+     * Provides a generic remove item button.
+     */
+    private static class RemoveItemButton extends Button {
+        public RemoveItemButton(final RepositoryObject object, final ObjectRepository repository) {
+            super("x");
+            setStyleName("small");
+
+            addListener(new Button.ClickListener() {
+                public void buttonClick(ClickEvent event) {
+                    try {
+                        repository.remove(object);
+                    }
+                    catch (Exception e) {
+                        // ACE-246: notify user when the removal failed!
+                        getWindow().showNotification("Failed to remove item!", "<br/>Reason: " + e.getMessage(),
+                            Notification.TYPE_ERROR_MESSAGE);
+                    }
+                }
+            });
+        }
+    }
+
+    /**
+     * Provides a generic remove-link (or association) button.
+     */
+    private class RemoveLinkButton<REPO_OBJECT extends RepositoryObject> extends Button {
+        public RemoveLinkButton(final REPO_OBJECT object, final Table toLeft, final Table toRight,
+            final BaseObjectPanel removeHandler) {
+            super("-");
+            setStyleName("small");
+
+            addListener(new Button.ClickListener() {
+                public void buttonClick(ClickEvent event) {
+                    Set<?> selection = m_associations.getActiveSelection();
+                    if (selection != null) {
+                        if (m_associations.isActiveTable(toLeft)) {
+                            for (Object item : selection) {
+                                RepositoryObject selected = m_associations.lookupInActiveSelection(item);
+                                removeHandler.removeLeftSideAssociation(object, selected);
+                            }
+                        }
+                        else if (m_associations.isActiveTable(toRight)) {
+                            for (Object item : selection) {
+                                RepositoryObject selected = m_associations.lookupInActiveSelection(item);
+                                removeHandler.removeRightSideAssocation(object, selected);
+                            }
+                        }
+                    }
+                }
+            });
+        }
+    }
+
+    /**
+     * Provides a small container for {@link UIExtensionFactory} instances.
+     */
+    private static class UIExtensionFactoryHolder implements Comparable<UIExtensionFactoryHolder> {
+        private final ServiceReference m_serviceRef;
+        private final WeakReference<UIExtensionFactory> m_extensionFactory;
+
+        public UIExtensionFactoryHolder(ServiceReference serviceRef, UIExtensionFactory extensionFactory) {
+            m_serviceRef = serviceRef;
+            m_extensionFactory = new WeakReference<UIExtensionFactory>(extensionFactory);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public int compareTo(UIExtensionFactoryHolder other) {
+            ServiceReference thatServiceRef = other.m_serviceRef;
+            ServiceReference thisServiceRef = m_serviceRef;
+            // Sort in reverse order so that the highest rankings come first...
+            return thatServiceRef.compareTo(thisServiceRef);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null) {
+                return false;
+            }
+            if (!(obj instanceof UIExtensionFactoryHolder)) {
+                return false;
+            }
+            UIExtensionFactoryHolder other = (UIExtensionFactoryHolder) obj;
+            return m_serviceRef.equals(other.m_serviceRef);
+        }
+
+        /**
+         * @return the {@link UIExtensionFactory}, can be <code>null</code> if it has been GC'd before this method call.
+         */
+        public UIExtensionFactory getUIExtensionFactory() {
+            return m_extensionFactory.get();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int hashCode() {
+            return m_serviceRef.hashCode() ^ m_extensionFactory.hashCode();
+        }
+    }
+
+    protected static final String WORKING_STATE_ICON = "workStateIcon";
+    protected static final String OBJECT_NAME = "name";
+    protected static final String OBJECT_DESCRIPTION = "description";
+    protected static final String ACTIONS = "actions";
+
+    protected static final int ICON_HEIGHT = 16;
+    protected static final int ICON_WIDTH = 16;
+
+    private final Associations m_associations;
+    protected final AssociationRemover m_associationRemover;
+
+    private final List<UIExtensionFactoryHolder> m_extensionFactories;
+    private final String m_extensionPoint;
+
+    private Button m_removeLinkButton;
+    private Button m_deleteButton;
+
+    private Table m_leftTable;
+    private Table m_rightTable;
+
+    /**
+     * Creates a new {@link BaseObjectPanel} instance.
+     * 
+     * @param associations the associations for this panel;
+     * @param associationRemover the association remove to use for removing associations;
+     * @param name the name of this panel;
+     * @param extensionPoint the extension point to listen for;
+     * @param hasEdit <code>true</code> if double clicking an row in this table should show an editor, <code>false</code> to disallow editing.
+     */
+    public BaseObjectPanel(final Associations associations, final AssociationRemover associationRemover,
+        final String name, final String extensionPoint, final boolean hasEdit) {
+        super(name + "s");
+
+        m_associations = associations;
+        m_associationRemover = associationRemover;
+        m_extensionFactories = new ArrayList<UIExtensionFactoryHolder>();
+        m_extensionPoint = extensionPoint;
+
+        defineTableColumns();
+
+        setSizeFull();
+        setCellStyleGenerator(m_associations.createCellStyleGenerator());
+        setSelectable(true);
+        setMultiSelect(true);
+        setImmediate(true);
+        setDragMode(TableDragMode.MULTIROW);
+
+        if (hasEdit) {
+            addListener(new ItemClickListener() {
+                public void itemClick(ItemClickEvent event) {
+                    if (event.isDoubleClick()) {
+                        String itemId = (String) event.getItemId();
+                        RepositoryObject object = getFromId(itemId);
+                        NamedObject namedObject = m_associations.getNamedObject(object);
+                        showEditWindow(namedObject);
+                    }
+                }
+            });
+        }
+    }
+
+    /**
+     * Called by the dependency manager in case a new {@link UIExtensionFactory} is registered.
+     * 
+     * @param ref the service reference of the new extension;
+     * @param factory the extension instance itself.
+     */
+    public final void addExtension(ServiceReference ref, UIExtensionFactory factory) {
+        synchronized (m_extensionFactories) {
+            m_extensionFactories.add(new UIExtensionFactoryHolder(ref, factory));
+        }
+        populate();
+    }
+
+    /**
+     * @see org.osgi.service.event.EventHandler#handleEvent(org.osgi.service.event.Event)
+     */
+    public final void handleEvent(org.osgi.service.event.Event event) {
+        final RepositoryObject entity = (RepositoryObject) event.getProperty(RepositoryObject.EVENT_ENTITY);
+        final String topic = event.getTopic();
+
+        synchronized (getApplication()) {
+            if (isSupportedEntity(entity)) {
+                try {
+                    handleEvent(topic, entity, event);
+                }
+                finally {
+                    refreshRenderedCells();
+                }
+            }
+        }
+    }
+
+    /**
+     * Called by the dependency manager upon initialization of this component.
+     * 
+     * @param component the component representing this object.
+     */
+    public void init(Component component) {
+        populate();
+
+        DependencyManager dm = component.getDependencyManager();
+        component.add(dm
+            .createServiceDependency()
+            .setInstanceBound(true)
+            .setService(UIExtensionFactory.class, "(" + UIExtensionFactory.EXTENSION_POINT_KEY + "=" + m_extensionPoint + ")")
+            .setCallbacks("addExtension", "removeExtension"));
+    }
+
+    /**
+     * Called to populate this table.
+     */
+    public void populate() {
+        removeAllItems();
+        for (REPO_OBJ object : getAllRepositoryObjects()) {
+            add(object);
+        }
+    }
+
+    /**
+     * Called by the dependency manager in case a {@link UIExtensionFactory} is unregistered.
+     * 
+     * @param ref the service reference of the extension;
+     * @param factory the extension instance itself.
+     */
+    public final void removeExtension(ServiceReference ref, UIExtensionFactory factory) {
+        synchronized (m_extensionFactories) {
+            m_extensionFactories.remove(new UIExtensionFactoryHolder(ref, factory));
+        }
+        populate();
+    }
+
+    /**
+     * Sets the left-side table, that defines the left-hand side of the assocations of the entities.
+     * 
+     * @param leftTable the table to set, can be <code>null</code>.
+     */
+    public final void setLeftTable(Table leftTable) {
+        m_leftTable = leftTable;
+    }
+
+    /**
+     * Sets the right-side table, that defines the right-hand side of the assocations of the entities.
+     * 
+     * @param rightTable the table to set, can be <code>null</code>.
+     */
+    public final void setRightTable(Table rightTable) {
+        m_rightTable = rightTable;
+    }
+
+    /**
+     * Removes the left-hand side associations for a given repository object.
+     * 
+     * @param object the repository object to remove the left-hand side associations;
+     * @param other the (left-hand side) repository object to remove the associations for.
+     */
+    final void removeLeftSideAssociation(REPO_OBJ object, RepositoryObject other) {
+        if (doRemoveLeftSideAssociation(object, other)) {
+            m_associations.removeAssociatedItem(object);
+            requestRepaint();
+        }
+    }
+
+    /**
+     * Removes the right-hand side associations for a given repository object.
+     * 
+     * @param object the repository object to remove the right-hand side associations;
+     * @param other the (right-hand side) repository object to remove the associations for.
+     */
+    final void removeRightSideAssocation(REPO_OBJ object, RepositoryObject other) {
+        if (doRemoveRightSideAssociation(object, other)) {
+            m_associations.removeAssociatedItem(object);
+            requestRepaint();
+        }
+    }
+
+    /**
+     * Adds a given repository object to this table.
+     * 
+     * @param object the repository object to add, cannot be <code>null</code>.
+     */
+    protected void add(REPO_OBJ object) {
+        Item item = addItem(object.getDefinition());
+        if (item != null) {
+            populateItem(object, item);
+        }
+    }
+
+    /**
+     * Creates the action buttons.
+     * 
+     * @param statefulTarget the target to create the action buttons for, cannot be <code>null</code>.
+     * @return a HorizontalLayout instance with action buttons.
+     */
+    protected HorizontalLayout createActionButtons(REPO_OBJ object) {
+        m_removeLinkButton = createRemoveLinkButton(object);
+        m_deleteButton = createRemoveItemButton(object);
+
+        HorizontalLayout buttons = new HorizontalLayout();
+        if (m_removeLinkButton != null) {
+            buttons.addComponent(m_removeLinkButton);
+        }
+        if (m_deleteButton != null) {
+            buttons.addComponent(m_deleteButton);
+        }
+        return buttons;
+    }
+    
+    protected abstract EditWindow createEditor(NamedObject object, List<UIExtensionFactory> extensions);
+
+    /**
+     * Factory method to create an embeddable icon.
+     * 
+     * @param name the name of the icon to use (is also used as tooltip text);
+     * @param res the resource denoting the actual icon.
+     * @return an embeddable icon, never <code>null</code>.
+     */
+    protected Embedded createIcon(String name, Resource res) {
+        Embedded embedded = new Embedded(name, res);
+        embedded.setType(Embedded.TYPE_IMAGE);
+        embedded.setDescription(name);
+        embedded.setHeight(ICON_HEIGHT + "px");
+        embedded.setWidth(ICON_WIDTH + "px");
+        return embedded;
+    }
+
+    /**
+     * Factory method to create an icon resource.
+     * 
+     * @param iconName the base name of the icon to use, it will be appended with '.png'.
+     * @return a {@link Resource} denoting the icon.
+     */
+    protected Resource createIconResource(String iconName) {
+        return new ThemeResource("icons/" + iconName.toLowerCase() + ".png");
+    }
+
+    /**
+     * Factory method to create a remove-item button.
+     * 
+     * @param object the repository object to create the remove-item button for, cannot be <code>null</code>.
+     * @return a button, can be <code>null</code> if removal of this repository object is not supported.
+     */
+    protected Button createRemoveItemButton(REPO_OBJ object) {
+        return new RemoveItemButton(object, getRepository());
+    }
+
+    /**
+     * Factory method to create a remove-link button.
+     * 
+     * @param object the repository object to create the remove-link button for, cannot be <code>null</code>.
+     * @return a button, can be <code>null</code> if remove-link is not supported.
+     */
+    protected Button createRemoveLinkButton(REPO_OBJ object) {
+        return new RemoveLinkButton<REPO_OBJ>(object, m_leftTable, m_rightTable, this);
+    }
+
+    /**
+     * Defines the table columns for this panel.
+     */
+    protected void defineTableColumns() {
+        addContainerProperty(WORKING_STATE_ICON, Embedded.class, null, "", null, ALIGN_CENTER);
+        addContainerProperty(OBJECT_NAME, String.class, null);
+        addContainerProperty(OBJECT_DESCRIPTION, String.class, null);
+        addContainerProperty(ACTIONS, HorizontalLayout.class, null);
+
+        setColumnWidth(WORKING_STATE_ICON, ICON_WIDTH);
+    }
+
+    /**
+     * Does the actual removal of the left-hand side associations for a given repository object.
+     * 
+     * @param object the repository object to remove the left-hand side associations;
+     * @param other the (left-hand side) repository object to remove the associations for.
+     * @return <code>true</code> if the associations were removed, <code>false</code> if not.
+     */
+    protected boolean doRemoveLeftSideAssociation(REPO_OBJ object, RepositoryObject other) {
+        return m_leftTable != null;
+    }
+
+    /**
+     * Does the actual removal of the right-hand side associations for a given repository object.
+     * 
+     * @param object the repository object to remove the right-hand side associations;
+     * @param other the (right-hand side) repository object to remove the associations for.
+     * @return <code>true</code> if the associations were removed, <code>false</code> if not.
+     */
+    protected boolean doRemoveRightSideAssociation(REPO_OBJ object, RepositoryObject other) {
+        return m_rightTable != null;
+    }
+
+    /**
+     * Converts a table-id back to a concrete {@link RepositoryObject}.
+     * 
+     * @param id the identifier of the {@link RepositoryObject}, cannot be <code>null</code>.
+     * @return a {@link RepositoryObject} instance for the given ID, can be <code>null</code> in case no such object is found.
+     */
+    protected final REPO_OBJ getFromId(String id) {
+        return getRepository().get(id);
+    }
+
+    /**
+     * Returns the actual repository for objects.
+     * 
+     * @return the actual repository for obtaining the repository objects, cannot be <code>null</code>.
+     */
+    protected abstract REPO getRepository();
+
+    /**
+     * Returns the repository administrator.
+     * 
+     * @return the repository admin, never <code>null</code>.
+     */
+    protected abstract RepositoryAdmin getRepositoryAdmin();
+
+    /**
+     * Determines the working state for the given repository object.
+     * 
+     * @param object the repository object to determine the working state for, cannot be <code>null</code>.
+     * @return the working state for the given repository object, never <code>null</code>.
+     */
+    protected WorkingState getWorkingState(RepositoryObject object) {
+        return getRepositoryAdmin().getWorkingState(object);
+    }
+
+    /**
+     * Helper method to return the working state icon for the given repository object.
+     * 
+     * @param object the repository object to get the icon for, cannot be <code>null</code>.
+     * @return an icon representing the working state of the given repository object, never <code>null</code>.
+     */
+    protected Embedded getWorkingStateIcon(RepositoryObject object) {
+        String name = getWorkingState(object).name();
+        Resource res = createIconResource("resource_workingstate_" + name);
+        return createIcon(name, res);
+    }
+
+    /**
+     * @param topic the topic of the event;
+     * @param entity the entity of the event;
+     * @param event the original event.
+     * 
+     * @see org.osgi.service.event.EventHandler#handleEvent(org.osgi.service.event.Event)
+     */
+    protected abstract void handleEvent(String topic, RepositoryObject entity, org.osgi.service.event.Event event);
+
+    /**
+     * Returns whether the given {@link RepositoryObject} can be handled by this panel.
+     * 
+     * @param entity the entity to test, cannot be <code>null</code>.
+     * @return <code>true</code> if the entity is supported by this panel, <code>false</code> if not.
+     */
+    protected abstract boolean isSupportedEntity(RepositoryObject entity);
+
+    /**
+     * Populates the given table item with information from the given repository object.
+     * 
+     * @param object the repository object to take the information from, cannot be <code>null</code>;
+     * @param item the table item to populate, cannot be <code>null</code>.
+     */
+    protected abstract void populateItem(REPO_OBJ object, Item item);
+
+    /**
+     * Removes a given repository object from this table.
+     * 
+     * @param object the repository object to remove, cannot be <code>null</code>.
+     */
+    protected void remove(REPO_OBJ object) {
+        removeItem(object.getDefinition());
+    }
+
+    /**
+     * Updates a given repository object in this table.
+     * 
+     * @param object the repository object to update, cannot be <code>null</code>.
+     */
+    protected void update(REPO_OBJ object) {
+        Item item = getItem(object.getDefinition());
+        if (item != null) {
+            populateItem(object, item);
+        }
+    }
+
+    /**
+     * Returns all repository objects.
+     * 
+     * @return an {@link Iterable} with all repository objects, never <code>null</code>.
+     */
+    private Iterable<REPO_OBJ> getAllRepositoryObjects() {
+        return getRepository().get();
+    }
+
+    /**
+     * @return a list of current extension factories, properly ordered, never <code>null</code>.
+     */
+    private List<UIExtensionFactory> getExtensionFactories() {
+        List<UIExtensionFactory> extensions;
+        synchronized (m_extensionFactories) {
+            // Sort the list of extension factories...
+            Collections.sort(m_extensionFactories);
+
+            // Walk through the holders and fetch the extension factories one by one...
+            extensions = new ArrayList<UIExtensionFactory>(m_extensionFactories.size());
+            for (UIExtensionFactoryHolder holder : m_extensionFactories) {
+                UIExtensionFactory extensionFactory = holder.getUIExtensionFactory();
+                // Make sure only to use non-GCd factories...
+                if (extensionFactory != null) {
+                    extensions.add(extensionFactory);
+                }
+            }
+        }
+        return extensions;
+    }
+
+    /**
+     * Shows an edit window for the given named object.
+     * 
+     * @param object the named object to edit;
+     * @param main the main window to use.
+     */
+    private void showEditWindow(NamedObject object) {
+        List<UIExtensionFactory> extensions = getExtensionFactories();
+        createEditor(object, extensions).show(getParent().getWindow());
+    }
+}
\ No newline at end of file

Added: ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/component/ConfirmationDialog.java
URL: http://svn.apache.org/viewvc/ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/component/ConfirmationDialog.java?rev=1357570&view=auto
==============================================================================
--- ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/component/ConfirmationDialog.java (added)
+++ ace/sandbox/marrs/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/component/ConfirmationDialog.java Thu Jul  5 12:09:30 2012
@@ -0,0 +1,133 @@
+/*
+ * 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.component;
+
+import com.vaadin.ui.AbstractComponent;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Button.ClickEvent;
+import com.vaadin.ui.Button.ClickListener;
+import com.vaadin.ui.GridLayout;
+import com.vaadin.ui.Label;
+import com.vaadin.ui.VerticalLayout;
+import com.vaadin.ui.Window;
+
+/**
+ * Provides a confirmation dialog, based on code found on <a href="https://vaadin.com/forum/-/message_boards/view_message/17883">this forum posting</a>.
+ */
+public class ConfirmationDialog extends Window implements ClickListener {
+
+    /**
+     * Callback class for a {@link ConfirmationDialog}.
+     */
+    public static interface Callback {
+        /**
+         * Called upon pressing a button.
+         * 
+         * @param buttonName the name of the button that was clicked, never <code>null</code>.
+         */
+        void onDialogResult(String buttonName);
+    }
+
+    public static final String YES = "Yes";
+    public static final String NO = "No";
+    public static final String CANCEL = "Cancel";
+
+    private final Callback m_callback;
+
+    /**
+     * Provides a Yes/No confirmation dialog.
+     * 
+     * @param caption the caption of this dialog, cannot be <code>null</code>;
+     * @param message the message to display, may be <code>null</code> to omit the message;
+     * @param callback the callback to call for each pressed button.
+     */
+    public ConfirmationDialog(String caption, String message, Callback callback) {
+        this(caption, message, callback, YES, NO);
+    }
+
+    /**
+     * Provides a confirmation dialog with a custom set of buttons.
+     * 
+     * @param caption the caption of this dialog, cannot be <code>null</code>;
+     * @param message the message to display, may be <code>null</code> to omit the message;
+     * @param callback the callback to call for each pressed button;
+     * @param buttonNames the names of the buttons to display.
+     */
+    public ConfirmationDialog(String caption, String message, Callback callback, String... buttonNames) {
+        super(caption);
+
+        if (buttonNames == null || buttonNames.length <= 1) {
+            throw new IllegalArgumentException("Need at least one button name!");
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("Need a callback!");
+        }
+
+        m_callback = callback;
+
+        setWidth("30em");
+        setModal(true);
+
+        VerticalLayout layout = (VerticalLayout) getContent();
+        layout.setMargin(true);
+        layout.setSpacing(true);
+
+        addComponents(message, buttonNames);
+    }
+
+    /**
+     * @see com.vaadin.ui.Button.ClickListener#buttonClick(com.vaadin.ui.Button.ClickEvent)
+     */
+    public void buttonClick(ClickEvent event) {
+        Window parent = getParent();
+        if (parent != null) {
+            parent.removeWindow(this);
+        }
+
+        AbstractComponent comp = (AbstractComponent) event.getComponent();
+        m_callback.onDialogResult((String) comp.getData());
+    }
+
+    /**
+     * Adds all components to this dialog.
+     * 
+     * @param message the optional message to display, can be <code>null</code>;
+     * @param buttonNames the names of the buttons to add, never <code>null</code> or empty.
+     */
+    protected void addComponents(String message, String... buttonNames) {
+        if (message != null) {
+            addComponent(new Label(message));
+        }
+
+        GridLayout gl = new GridLayout(buttonNames.length + 1, 1);
+        gl.setSpacing(true);
+        gl.setWidth("100%");
+
+        gl.addComponent(new Label(" "));
+        gl.setColumnExpandRatio(0, 1.0f);
+
+        for (String buttonName : buttonNames) {
+            Button button = new Button(buttonName, this);
+            button.setData(buttonName);
+            gl.addComponent(button);
+        }
+
+        addComponent(gl);
+    }
+}



Mime
View raw message