You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ace.apache.org by ma...@apache.org on 2012/07/05 14:10:06 UTC
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...
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);
+ }
+}