You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ace.apache.org by ja...@apache.org on 2013/10/28 18:03:54 UTC

svn commit: r1536411 [2/3] - in /ace/trunk: org.apache.ace.client.repository/src/org/apache/ace/client/repository/impl/ org.apache.ace.client.repository/src/org/apache/ace/client/repository/repository/ org.apache.ace.webui.vaadin/src/org/apache/ace/web...

Added: ace/trunk/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/UploadHelper.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/UploadHelper.java?rev=1536411&view=auto
==============================================================================
--- ace/trunk/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/UploadHelper.java (added)
+++ ace/trunk/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/UploadHelper.java Mon Oct 28 17:03:53 2013
@@ -0,0 +1,387 @@
+/*
+ * 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.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import org.apache.ace.client.repository.object.ArtifactObject;
+import org.apache.ace.client.repository.repository.ArtifactRepository;
+
+import com.vaadin.event.dd.DragAndDropEvent;
+import com.vaadin.event.dd.DropHandler;
+import com.vaadin.event.dd.acceptcriteria.AcceptAll;
+import com.vaadin.event.dd.acceptcriteria.AcceptCriterion;
+import com.vaadin.terminal.StreamVariable;
+import com.vaadin.ui.DragAndDropWrapper.WrapperTransferable;
+import com.vaadin.ui.Html5File;
+import com.vaadin.ui.Upload;
+import com.vaadin.ui.Upload.FailedEvent;
+import com.vaadin.ui.Upload.FinishedEvent;
+import com.vaadin.ui.Upload.SucceededEvent;
+
+/**
+ * Provides convenience and utility methods for handling uploads of artifacts.
+ */
+public class UploadHelper {
+    public static interface UploadHandle {
+        void cleanup();
+        
+        Exception getFailureReason();
+
+        File getFile();
+        
+        String getFilename();
+
+        boolean isSuccessful();
+    }
+
+    /**
+     * Provides a {@link DropHandler} implementation for handling dropped artifacts.
+     */
+    static final class ArtifactDropHandler implements DropHandler {
+        private final GenericUploadHandler m_uploadHandler;
+
+        ArtifactDropHandler(GenericUploadHandler uploadHandler) {
+            m_uploadHandler = uploadHandler;
+        }
+
+        @Override
+        public void drop(DragAndDropEvent dropEvent) {
+            // expecting this to be an html5 drag
+            WrapperTransferable tr = (WrapperTransferable) dropEvent.getTransferable();
+            Html5File[] files = tr.getFiles();
+            if (files != null) {
+                for (Html5File html5File : files) {
+                    StreamVariable streamVar = m_uploadHandler.prepareUpload(html5File);
+
+                    html5File.setStreamVariable(streamVar);
+                }
+            }
+        }
+
+        @Override
+        public AcceptCriterion getAcceptCriterion() {
+            return AcceptAll.get();
+        }
+    }
+
+    /**
+     * Provides a upload handler capable of handling "old school" uploads, and new HTML5-style uploads.
+     */
+    static abstract class GenericUploadHandler implements Upload.SucceededListener,
+        Upload.FailedListener, Upload.Receiver, Upload.FinishedListener, Upload.ProgressListener {
+        private final Map<String, UploadHandleImpl> m_uploads = new ConcurrentHashMap<String, UploadHandleImpl>();
+        private final List<UploadHandle> m_completed = new CopyOnWriteArrayList<UploadHandle>();
+        private final File m_sessionDir;
+
+        /**
+         * @param sessionDir
+         *            the session directory to temporarily store uploaded artifacts in, cannot be <code>null</code>.
+         */
+        GenericUploadHandler(File sessionDir) {
+            m_sessionDir = sessionDir;
+        }
+
+        /**
+         * Install this upload handler for the given {@link Upload} component.
+         * 
+         * @param upload
+         *            the upload component to install this handler on, cannot be <code>null</code>.
+         */
+        public void install(Upload upload) {
+            upload.setReceiver(this);
+            upload.addListener((Upload.FailedListener) this);
+            upload.addListener((Upload.FinishedListener) this);
+            upload.addListener((Upload.ProgressListener) this);
+            upload.addListener((Upload.SucceededListener) this);
+        }
+
+        @Override
+        public final OutputStream receiveUpload(String filename, String MIMEType) {
+            UploadHandleImpl handle = new UploadHandleImpl(this, new File(m_sessionDir, filename));
+            m_uploads.put(filename, handle);
+            handle.startUpload();
+            uploadStarted(handle);
+            return handle.m_fos;
+        }
+
+        @Override
+        public final void uploadFailed(FailedEvent event) {
+            handleUploadFailure(event.getFilename(), event.getReason());
+        }
+
+        @Override
+        public final void uploadFinished(FinishedEvent event) {
+            UploadHandleImpl handle = m_uploads.get(event.getFilename());
+            if (handle != null) {
+                silentlyClose(handle);
+            }
+        }
+
+        @Override
+        public final void uploadSucceeded(SucceededEvent event) {
+            handleUploadSuccess(event.getFilename());
+        }
+
+        final void handleUploadFailure(String filename, Exception reason) {
+            UploadHandleImpl handle = m_uploads.remove(filename);
+            if (handle != null) {
+                handle.uploadFailed(reason);
+
+                silentlyClose(handle);
+
+                m_completed.add(handle);
+            }
+
+            if (m_uploads.isEmpty()) {
+                // All uploads are finished...
+                artifactsUploaded(m_completed);
+                m_completed.clear();
+            }
+        }
+
+        final void handleUploadSuccess(String filename) {
+            UploadHandleImpl handle = m_uploads.remove(filename);
+            if (handle != null) {
+                silentlyClose(handle);
+
+                m_completed.add(handle);
+            }
+
+            if (m_uploads.isEmpty()) {
+                // All uploads are finished...
+                artifactsUploaded(m_completed);
+                m_completed.clear();
+            }
+        }
+
+        final UploadHandleImpl prepareUpload(Html5File file) {
+            String fileName = file.getFileName();
+            UploadHandleImpl uploadHandle = new UploadHandleImpl(this, new File(m_sessionDir, fileName));
+            m_uploads.put(fileName, uploadHandle);
+            return uploadHandle;
+        }
+
+        /**
+         * Called when the upload is finished. The exact status (success or failure) can be determined by the given
+         * {@link UploadHandle}.
+         * 
+         * @param handles
+         *            the uploaded artifacts to process, never <code>null</code>.
+         */
+        protected abstract void artifactsUploaded(List<UploadHandle> handles);
+
+        /**
+         * Called when the upload is started for an artifact.
+         * 
+         * @param handle
+         *            the handle to the upload that is started, never <code>null</code>.
+         */
+        protected void uploadStarted(UploadHandle handle) {
+            // Nop
+        }
+    }
+
+    private static class UploadHandleImpl implements StreamVariable, Closeable, UploadHandle {
+        private final GenericUploadHandler m_handler;
+        private final String m_filename;
+        private final File m_file;
+
+        private FileOutputStream m_fos = null;
+        private Exception m_failureReason = null;
+
+        /**
+         * Creates a new {@link UploadHandleImpl} instance.
+         */
+        public UploadHandleImpl(GenericUploadHandler handler, File file) {
+            m_handler = handler;
+            m_filename = file.getName();
+            m_file = file;
+            m_fos = null;
+        }
+
+        @Override
+        public void cleanup() {
+            m_file.delete();
+            silentlyClose(m_fos);
+            m_fos = null;
+        }
+
+        @Override
+        public void close() throws IOException {
+            if (m_fos != null) {
+                m_fos.close();
+            }
+        }
+        
+        @Override
+        public Exception getFailureReason() {
+            return m_failureReason;
+        }
+        
+        @Override
+        public File getFile() {
+            return m_file;
+        }
+
+        @Override
+        public String getFilename() {
+            return m_filename;
+        }
+
+        @Override
+        public OutputStream getOutputStream() {
+            return m_fos;
+        }
+
+        @Override
+        public boolean isInterrupted() {
+            return (m_fos == null);
+        }
+
+        @Override
+        public boolean isSuccessful() {
+            return (m_failureReason == null);
+        }
+
+        @Override
+        public boolean listenProgress() {
+            return true;
+        }
+
+        @Override
+        public void onProgress(StreamingProgressEvent event) {
+            m_handler.updateProgress(event.getBytesReceived(), event.getContentLength());
+        }
+
+        @Override
+        public void streamingFailed(StreamingErrorEvent event) {
+            uploadFailed(event.getException());
+            m_handler.handleUploadFailure(m_filename, event.getException());
+        }
+
+        @Override
+        public void streamingFinished(StreamingEndEvent event) {
+            m_handler.handleUploadSuccess(m_filename);
+        }
+
+        @Override
+        public void streamingStarted(StreamingStartEvent event) {
+            startUpload();
+
+            if (m_fos != null) {
+                m_handler.uploadStarted(this);
+            }
+        }
+
+        /**
+         * Starts the upload by creating an output stream to the temporary file.
+         */
+        void startUpload() {
+            try {
+                m_fos = new FileOutputStream(m_file);
+            }
+            catch (FileNotFoundException exception) {
+                // This can only happen in theory...
+                m_failureReason = exception;
+                m_fos = null;
+            }
+        }
+
+        void uploadFailed(Exception reason) {
+            m_failureReason = reason;
+        }
+    }
+
+    /**
+     * Imports a local bundle (already contained in the OBR) bundle.
+     * 
+     * @param artifactURL
+     *            the URL of the artifact to import, cannot be <code>null</code>.
+     * @return the imported artifact object, never <code>null</code>.
+     * @throws IOException
+     *             in case an I/O exception has occurred.
+     */
+    public static ArtifactObject importLocalBundle(ArtifactRepository artifactRepository, File artifact) throws IOException {
+        URL url = artifact.toURI().toURL();
+        if (!artifactRepository.recognizeArtifact(url)) {
+            throw new IOException("Artifact " + artifact.getName() + " not recognized!");
+        }
+        return importLocalBundle(artifactRepository, url);
+    }
+
+    /**
+     * Imports a local bundle (already contained in the OBR) bundle.
+     * 
+     * @param artifactURL
+     *            the URL of the artifact to import, cannot be <code>null</code>.
+     * @return the imported artifact object, never <code>null</code>.
+     * @throws IOException
+     *             in case an I/O exception has occurred.
+     */
+    public static ArtifactObject importLocalBundle(ArtifactRepository artifactRepository, URL url) throws IOException {
+        return artifactRepository.importArtifact(url, false /* upload */);
+    }
+
+    /**
+     * Imports a remote bundle by uploading it to the OBR.
+     * 
+     * @param artifactURL
+     *            the URL of the artifact to import, cannot be <code>null</code>.
+     * @return the imported artifact object, never <code>null</code>.
+     * @throws IOException
+     *             in case an I/O exception has occurred.
+     */
+    public static ArtifactObject importRemoteBundle(ArtifactRepository artifactRepository, File artifact) throws IOException {
+        URL url = artifact.toURI().toURL();
+        if (!artifactRepository.recognizeArtifact(url)) {
+            throw new IOException("Artifact " + artifact.getName() + " not recognized!");
+        }
+        return artifactRepository.importArtifact(url, true /* upload */);
+    }
+
+    /**
+     * Silently closes the given {@link Closeable} implementation, ignoring any errors that come out of the
+     * {@link Closeable#close()} method.
+     * 
+     * @param closable
+     *            the closeable to close, can be <code>null</code>.
+     */
+    private static void silentlyClose(Closeable closable) {
+        if (closable != null) {
+            try {
+                closable.close();
+            }
+            catch (IOException e) {
+                // Best effort; nothing we can (or want) do about this...
+            }
+        }
+    }
+
+}

Propchange: ace/trunk/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/UploadHelper.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: ace/trunk/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/VaadinClient.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/VaadinClient.java?rev=1536411&r1=1536410&r2=1536411&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/VaadinClient.java (original)
+++ ace/trunk/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/VaadinClient.java Mon Oct 28 17:03:53 2013
@@ -22,9 +22,9 @@ import java.io.File;
 import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
-import java.net.URLConnection;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Properties;
@@ -46,6 +46,7 @@ import org.apache.ace.client.repository.
 import org.apache.ace.client.repository.object.TargetObject;
 import org.apache.ace.client.repository.repository.Artifact2FeatureAssociationRepository;
 import org.apache.ace.client.repository.repository.ArtifactRepository;
+import org.apache.ace.client.repository.repository.ArtifactRepository.ArtifactAlreadyExistsException;
 import org.apache.ace.client.repository.repository.Distribution2TargetAssociationRepository;
 import org.apache.ace.client.repository.repository.DistributionRepository;
 import org.apache.ace.client.repository.repository.Feature2DistributionAssociationRepository;
@@ -56,11 +57,15 @@ import org.apache.ace.connectionfactory.
 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.UploadHelper.ArtifactDropHandler;
+import org.apache.ace.webui.vaadin.UploadHelper.GenericUploadHandler;
+import org.apache.ace.webui.vaadin.UploadHelper.UploadHandle;
 import org.apache.ace.webui.vaadin.component.ArtifactsPanel;
 import org.apache.ace.webui.vaadin.component.AssociationHelper;
 import org.apache.ace.webui.vaadin.component.DistributionsPanel;
 import org.apache.ace.webui.vaadin.component.FeaturesPanel;
 import org.apache.ace.webui.vaadin.component.MainActionToolbar;
+import org.apache.ace.webui.vaadin.component.StatusLine;
 import org.apache.ace.webui.vaadin.component.TargetsPanel;
 import org.apache.felix.dm.Component;
 import org.apache.felix.dm.DependencyManager;
@@ -72,54 +77,62 @@ import org.osgi.service.useradmin.Author
 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.service.ApplicationContext;
-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.DragAndDropWrapper;
 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;
+import com.vaadin.ui.Window.Notification;
 
-/*
-
- 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)
+/**
+ * Main application entry point.
  */
 @SuppressWarnings("serial")
-public class VaadinClient extends com.vaadin.Application implements AssociationRemover, LoginFunction {
+public class VaadinClient extends com.vaadin.Application implements AssociationManager, LoginFunction {
 
     private static final long serialVersionUID = 1L;
 
     private static long SESSION_ID = 1;
-
     private static final String targetRepo = "target";
     private static final String shopRepo = "shop";
     private static final String deployRepo = "deployment";
     private static final String customerName = "apache";
+
     private static final String endpoint = "/repository";
 
+    // basic session ID generator
+    private static long generateSessionID() {
+        return SESSION_ID++;
+    }
+
+    /**
+     * Remove the given directory and all it's files and subdirectories
+     * 
+     * @param directory
+     *            the name of the directory to remove
+     */
+    private static void removeDirectoryWithContent(File directory) {
+        if ((directory == null) || !directory.exists()) {
+            return;
+        }
+        File[] filesAndSubDirs = directory.listFiles();
+        for (int i = 0; i < filesAndSubDirs.length; i++) {
+            File file = filesAndSubDirs[i];
+            if (file.isDirectory()) {
+                removeDirectoryWithContent(file);
+            }
+            // else just remove the file
+            file.delete();
+        }
+        directory.delete();
+    }
+
     private volatile AuthenticationService m_authenticationService;
     private volatile BundleContext m_context;
     private volatile SessionFactory m_sessionFactory;
@@ -131,16 +144,19 @@ public class VaadinClient extends com.va
     private volatile Artifact2FeatureAssociationRepository m_artifact2featureAssociationRepository;
     private volatile Feature2DistributionAssociationRepository m_feature2distributionAssociationRepository;
     private volatile Distribution2TargetAssociationRepository m_distribution2targetAssociationRepository;
+
     private volatile RepositoryAdmin m_admin;
     private volatile LogService m_log;
     private volatile ConnectionFactory m_connectionFactory;
 
     private String m_sessionID;
+
     private ArtifactsPanel m_artifactsPanel;
     private FeaturesPanel m_featuresPanel;
     private DistributionsPanel m_distributionsPanel;
     private TargetsPanel m_targetsPanel;
     private GridLayout m_grid;
+    private StatusLine m_statusLine;
     private boolean m_dynamicRelations = true;
     private File m_sessionDir; // private folder for session info
     private HorizontalLayout m_artifactToolbar;
@@ -148,50 +164,25 @@ public class VaadinClient extends com.va
     private Button m_distributionToolbar;
     private Button m_targetToolbar;
     private Window m_mainWindow;
-
     private final URL m_obrUrl;
     private final String m_repositoryXML;
+
     private final URL m_repository;
     private final boolean m_useAuth;
-    private final String m_userName;
 
+    private final String m_userName;
     private final AssociationHelper m_associations = new AssociationHelper();
     private final AtomicBoolean m_dependenciesResolved = new AtomicBoolean(false);
-
     private ProgressIndicator m_progress;
+
     private DependencyManager m_manager;
+
     private Component m_component;
+
     private final List<Component> m_eventHandlers = new ArrayList<Component>();
 
     private GridLayout m_mainToolbar;
 
-    // basic session ID generator
-    private static long generateSessionID() {
-        return SESSION_ID++;
-    }
-
-    /**
-     * Remove the given directory and all it's files and subdirectories
-     * 
-     * @param directory
-     *            the name of the directory to remove
-     */
-    private static void removeDirectoryWithContent(File directory) {
-        if ((directory == null) || !directory.exists()) {
-            return;
-        }
-        File[] filesAndSubDirs = directory.listFiles();
-        for (int i = 0; i < filesAndSubDirs.length; i++) {
-            File file = filesAndSubDirs[i];
-            if (file.isDirectory()) {
-                removeDirectoryWithContent(file);
-            }
-            // else just remove the file
-            file.delete();
-        }
-        directory.delete();
-    }
-
     /**
      * Creates a new {@link VaadinClient} instance.
      * 
@@ -225,31 +216,6 @@ public class VaadinClient extends com.va
     }
 
     @Override
-    public void start(URL applicationUrl, Properties applicationProperties, ApplicationContext context) {
-        m_component = m_manager.createComponent()
-            .setImplementation(this)
-            .setCallbacks("setupDependencies", "start", "stop", "destroyDependencies")
-            .add(m_manager.createServiceDependency()
-                .setService(SessionFactory.class)
-                .setRequired(true)
-            )
-            .add(m_manager.createServiceDependency()
-                .setService(UserAdmin.class)
-                .setRequired(true)
-            )
-            .add(m_manager.createServiceDependency()
-                .setService(AuthenticationService.class)
-                .setRequired(m_useAuth)
-            )
-            .add(m_manager.createServiceDependency()
-                .setService(LogService.class)
-                .setRequired(false)
-            );
-        m_manager.add(m_component);
-        super.start(applicationUrl, applicationProperties, context);
-    }
-
-    @Override
     public void close() {
         if (isRunning()) {
             m_admin.deleteLocal();
@@ -259,44 +225,35 @@ public class VaadinClient extends com.va
         }
     }
 
-    public void setupDependencies(Component component) {
-        m_sessionID = "web-" + generateSessionID();
-        File dir = m_context.getDataFile(m_sessionID);
-        dir.mkdir();
-        m_sessionDir = dir;
-        m_sessionFactory.createSession(m_sessionID, null);
-        addSessionDependency(component, RepositoryAdmin.class);
-        addSessionDependency(component, DistributionRepository.class);
-        addSessionDependency(component, ArtifactRepository.class);
-        addSessionDependency(component, FeatureRepository.class);
-        addSessionDependency(component, Artifact2FeatureAssociationRepository.class);
-        addSessionDependency(component, Feature2DistributionAssociationRepository.class);
-        addSessionDependency(component, Distribution2TargetAssociationRepository.class);
-        addSessionDependency(component, 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() throws Exception {
-        m_log.log(LogService.LOG_INFO, "Stopping session #" + m_sessionID);
-
-        try {
-            close();
-
-            try {
-                m_admin.logout(true /* force */);
+    @Override
+    public void createArtifact2FeatureAssociation(ArtifactObject artifact, FeatureObject feature) {
+        // 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, feature, null);
             }
-            catch (IllegalStateException exception) {
-                // Ignore, we're already logged out...
+            else {
+                m_artifact2featureAssociationRepository.create(artifact, feature);
             }
         }
-        finally {
-            m_dependenciesResolved.set(false);
+    }
+
+    @Override
+    public void createDistribution2TargetAssociation(DistributionObject distribution, StatefulTargetObject target) {
+        if (!target.isRegistered()) {
+            target.register();
+            target.setAutoApprove(true);
         }
+        m_distribution2targetAssociationRepository.create(distribution, target.getTargetObject());
+    }
+
+    @Override
+    public void createFeature2DistributionAssociation(FeatureObject feature, DistributionObject distribution) {
+        m_feature2distributionAssociationRepository.create(feature, distribution);
     }
 
     public void destroyDependencies() {
@@ -312,12 +269,10 @@ public class VaadinClient extends com.va
             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>");
+            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
@@ -336,237 +291,144 @@ public class VaadinClient extends com.va
     }
 
     /**
-     * Shows the login window on the center of the main window.
+     * {@inheritDoc}
      */
-    private void showLoginWindow() {
-        LoginWindow loginWindow = new LoginWindow(m_log, this);
-
-        m_mainWindow.addWindow(loginWindow);
+    public boolean login(String username, String password) {
+        User user = m_authenticationService.authenticate(username, password);
+        setUser(user);
+        return login(user);
+    }
 
-        loginWindow.center();
+    /**
+     * {@inheritDoc}
+     */
+    public void removeAssociation(Artifact2FeatureAssociation association) {
+        m_artifact2featureAssociationRepository.remove(association);
     }
 
-    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();
+    /**
+     * {@inheritDoc}
+     */
+    public void removeAssociation(Distribution2TargetAssociation association) {
+        m_distribution2targetAssociationRepository.remove(association);
+    }
 
-        m_mainToolbar = createToolbar(user);
-        m_grid.addComponent(m_mainToolbar, 0, 0, count - 1, 0);
+    /**
+     * {@inheritDoc}
+     */
+    public void removeAssociation(Feature2DistributionAssociation association) {
+        m_feature2distributionAssociationRepository.remove(association);
+    }
 
-        m_artifactsPanel = createArtifactsPanel(user);
+    public void setupDependencies(Component component) {
+        m_sessionID = "web-" + generateSessionID();
+        File dir = m_context.getDataFile(m_sessionID);
+        dir.mkdir();
+        m_sessionDir = dir.getAbsoluteFile();
+        m_sessionFactory.createSession(m_sessionID, null);
+        addSessionDependency(component, RepositoryAdmin.class);
+        addSessionDependency(component, DistributionRepository.class);
+        addSessionDependency(component, ArtifactRepository.class);
+        addSessionDependency(component, FeatureRepository.class);
+        addSessionDependency(component, Artifact2FeatureAssociationRepository.class);
+        addSessionDependency(component, Feature2DistributionAssociationRepository.class);
+        addSessionDependency(component, Distribution2TargetAssociationRepository.class);
+        addSessionDependency(component, StatefulTargetRepository.class);
+        addDependency(component, ConnectionFactory.class);
+    }
 
-        m_artifactToolbar = new HorizontalLayout();
-        m_artifactToolbar.addComponent(createAddArtifactButton(user));
+    public void start() {
+        m_log.log(LogService.LOG_INFO, "Starting session #" + m_sessionID);
+        m_dependenciesResolved.set(true);
+    }
 
-        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);
+    @Override
+    public void start(URL applicationUrl, Properties applicationProperties, ApplicationContext context) {
+        m_component = m_manager.createComponent()
+            .setImplementation(this)
+            .setCallbacks("setupDependencies", "start", "stop", "destroyDependencies")
+            .add(m_manager.createServiceDependency()
+                .setService(SessionFactory.class)
+                .setRequired(true)
+            )
+            .add(m_manager.createServiceDependency()
+                .setService(UserAdmin.class)
+                .setRequired(true)
+            )
+            .add(m_manager.createServiceDependency()
+                .setService(AuthenticationService.class)
+                .setRequired(m_useAuth)
+            )
+            .add(m_manager.createServiceDependency()
+                .setService(LogService.class)
+                .setRequired(false)
+            );
+        m_manager.add(m_component);
+        super.start(applicationUrl, applicationProperties, context);
+    }
 
-        count = 0;
-        if (auth.hasRole("viewArtifact")) {
-            m_grid.addComponent(m_artifactsPanel, count, 2);
-            m_grid.addComponent(m_artifactToolbar, count, 1);
-            count++;
-        }
+    public void stop() throws Exception {
+        m_log.log(LogService.LOG_INFO, "Stopping session #" + m_sessionID);
 
-        m_featuresPanel = createFeaturesPanel(user);
-        m_featureToolbar = createAddFeatureButton(user);
+        try {
+            close();
 
-        if (auth.hasRole("viewFeature")) {
-            m_grid.addComponent(m_featuresPanel, count, 2);
-            m_grid.addComponent(m_featureToolbar, count, 1);
-            count++;
+            try {
+                m_admin.logout(true /* force */);
+            }
+            catch (IllegalStateException exception) {
+                // Ignore, we're already logged out...
+            }
+        }
+        finally {
+            m_dependenciesResolved.set(false);
         }
-
-        m_distributionsPanel = createDistributionsPanel(user);
-        m_distributionToolbar = createAddDistributionButton(user);
-
-        if (auth.hasRole("viewDistribution")) {
-            m_grid.addComponent(m_distributionsPanel, count, 2);
-            m_grid.addComponent(m_distributionToolbar, count, 1);
-            count++;
-        }
-
-        m_targetsPanel = createTargetsPanel(user);
-        m_targetToolbar = createAddTargetButton(user);
-
-        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_mainToolbar, RepositoryObject.PUBLIC_TOPIC_ROOT.concat(RepositoryObject.TOPIC_ALL_SUFFIX));
-        addListener(m_artifactsPanel, ArtifactObject.TOPIC_ALL, RepositoryAdmin.TOPIC_STATUSCHANGED, RepositoryAdmin.TOPIC_LOGIN, RepositoryAdmin.TOPIC_REFRESH);
-        addListener(m_featuresPanel, FeatureObject.TOPIC_ALL, RepositoryAdmin.TOPIC_STATUSCHANGED, RepositoryAdmin.TOPIC_LOGIN, RepositoryAdmin.TOPIC_REFRESH);
-        addListener(m_distributionsPanel, DistributionObject.TOPIC_ALL, RepositoryAdmin.TOPIC_STATUSCHANGED, RepositoryAdmin.TOPIC_LOGIN, RepositoryAdmin.TOPIC_REFRESH);
-        addListener(m_targetsPanel, StatefulTargetObject.TOPIC_ALL, TargetObject.TOPIC_ALL, RepositoryAdmin.TOPIC_STATUSCHANGED, RepositoryAdmin.TOPIC_LOGIN, RepositoryAdmin.TOPIC_REFRESH);
-
-        m_mainWindow.addComponent(m_grid);
-    }
+    }
 
     /**
-     * {@inheritDoc}
-     */
-    public void removeAssociation(Artifact2FeatureAssociation association) {
-        m_artifact2featureAssociationRepository.remove(association);
-    }
-
-    /**
-     * {@inheritDoc}
+     * Create a new distribution in the distribution repository
+     * 
+     * @param name
+     *            the name of the new distribution;
+     * @param description
+     *            the description of the new distribution.
      */
-    public void removeAssociation(Distribution2TargetAssociation association) {
-        m_distribution2targetAssociationRepository.remove(association);
+    protected DistributionObject 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>();
+        return m_distributionRepository.create(attributes, tags);
     }
 
     /**
-     * {@inheritDoc}
+     * Create a new feature in the feature repository.
+     * 
+     * @param name
+     *            the name of the new feature;
+     * @param description
+     *            the description of the new feature.
      */
-    public void removeAssociation(Feature2DistributionAssociation association) {
-        m_feature2distributionAssociationRepository.remove(association);
+    protected FeatureObject 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>();
+        return m_featureRepository.create(attributes, tags);
     }
 
     /**
-     * {@inheritDoc}
+     * Create a new target in the stateful target repository.
+     * 
+     * @param name
+     *            the name of the new target;
      */
-    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));
+    protected StatefulTargetObject 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>();
+        return m_statefulTargetRepository.preregister(attributes, tags);
     }
 
     private void addDependency(Component component, Class service) {
@@ -576,15 +438,6 @@ public class VaadinClient extends com.va
             .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);
@@ -598,15 +451,11 @@ public class VaadinClient extends com.va
         m_manager.add(component);
     }
 
-    private void cleanupListeners() {
-        Component[] components;
-        synchronized (m_eventHandlers) {
-            components = m_eventHandlers.toArray(new Component[m_eventHandlers.size()]);
-            m_eventHandlers.clear();
-        }
-        for (Component component : components) {
-            m_manager.remove(component);
-        }
+    private void addSessionDependency(Component component, Class service) {
+        component.add(m_manager.createServiceDependency()
+            .setService(service, "(" + SessionFactory.SERVICE_SID + "=" + m_sessionID + ")")
+            .setRequired(true)
+            .setInstanceBound(true));
     }
 
     /**
@@ -622,44 +471,128 @@ public class VaadinClient extends com.va
         }
     }
 
-    private GridLayout createToolbar(User user) {
-        final boolean showLogoutButton = m_useAuth;
-        final DependencyManager manager = m_manager;
-        MainActionToolbar mainActionToolbar = new MainActionToolbar(user, manager, showLogoutButton) {
-            @Override
-            protected RepositoryAdmin getRepositoryAdmin() {
-                return m_admin;
-            }
+    private void cleanupListeners() {
+        Component[] components;
+        synchronized (m_eventHandlers) {
+            components = m_eventHandlers.toArray(new Component[m_eventHandlers.size()]);
+            m_eventHandlers.clear();
+        }
+        for (Component component : components) {
+            m_manager.remove(component);
+        }
+    }
 
-            @Override
-            protected void doAfterRevert() throws IOException {
-                updateTableData();
+    /**
+     * Create a button to show a pop window for adding new features.
+     * 
+     * @param user
+     * 
+     * @param main
+     *            Main Window
+     * @return Button
+     */
+    private Button createAddArtifactButton(User user) {
+        Button button = new Button("Add artifact...");
+        button.addListener(new Button.ClickListener() {
+            public void buttonClick(ClickEvent event) {
+                showAddArtifactDialog();
             }
+        });
+        return button;
+    }
 
-            @Override
-            protected void doAfterRetrieve() throws IOException {
-                updateTableData();
-            }
+    /**
+     * Create a button to show a popup window for adding a new distribution. On success this calls the
+     * createDistribution() method.
+     * 
+     * @param user
+     * 
+     * @return the add-distribution button instance.
+     */
+    private Button createAddDistributionButton(User user) {
+        Button button = new Button("Add Distribution...");
+        button.addListener(new Button.ClickListener() {
+            public void buttonClick(ClickEvent event) {
+                GenericAddWindow window = new GenericAddWindow("Add Distribution") {
+                    public void handleError(Exception e) {
+                        // ACE-241: notify user when the distribution-creation failed!
+                        getWindow().showNotification("Failed to add new distribution!",
+                            "<br/>Reason: " + e.getMessage(), Notification.TYPE_ERROR_MESSAGE);
+                    }
 
-            @Override
-            protected void doAfterCommit() throws IOException {
-                updateTableData();
+                    public void onOk(String name, String description) {
+                        createDistribution(name, description);
+                    }
+                };
+                window.show(getMainWindow());
             }
+        });
 
-            @Override
-            protected void doAfterLogout() throws IOException {
-                // Close the application and reload the main window...
-                close();
+        return button;
+    }
+
+    /***
+     * Create a button to show popup window for adding a new feature. On success this calls the createFeature() method.
+     * 
+     * @param user
+     * 
+     * @return the add-feature button instance.
+     */
+    private Button createAddFeatureButton(User user) {
+        Button button = new Button("Add Feature...");
+        button.addListener(new Button.ClickListener() {
+            public void buttonClick(ClickEvent event) {
+                GenericAddWindow window = new GenericAddWindow("Add Feature") {
+                    public void handleError(Exception e) {
+                        // ACE-241: notify user when the feature-creation failed!
+                        getWindow().showNotification("Failed to add new feature!", "<br/>Reason: " + e.getMessage(),
+                            Notification.TYPE_ERROR_MESSAGE);
+                    }
+
+                    public void onOk(String name, String description) {
+                        createFeature(name, description);
+                    }
+                };
+                window.show(getMainWindow());
             }
+        });
+        return button;
+    }
 
-            private void updateTableData() {
-                m_artifactsPanel.populate();
-                m_featuresPanel.populate();
-                m_distributionsPanel.populate();
-                m_targetsPanel.populate();
+    /**
+     * Create a button to show a popup window for adding a new target. On success this calls the createTarget() method
+     * 
+     * @param user
+     * 
+     * @return the add-target button instance.
+     */
+    private Button createAddTargetButton(User user) {
+        Button button = new Button("Add target...");
+        button.addListener(new Button.ClickListener() {
+            public void buttonClick(ClickEvent event) {
+                GenericAddWindow window = new GenericAddWindow("Add Target") {
+                    protected void handleError(Exception e) {
+                        // ACE-241: notify user when the target-creation failed!
+                        getWindow().showNotification("Failed to add new target!", "<br/>Reason: " + e.getMessage(),
+                            Notification.TYPE_ERROR_MESSAGE);
+                    }
+
+                    @Override
+                    protected void initDialog() {
+                        m_name.setCaption("Identifier");
+                        m_description.setVisible(false);
+
+                        super.initDialog();
+                    }
+
+                    protected void onOk(String id, String description) {
+                        createTarget(id);
+                    }
+                };
+                window.show(getMainWindow());
             }
-        };
-        return mainActionToolbar;
+        });
+        return button;
     }
 
     private ArtifactsPanel createArtifactsPanel(User user) {
@@ -668,15 +601,15 @@ public class VaadinClient extends com.va
             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 void onOk(String name, String description) throws Exception {
+                        object.setDescription(description);
+                    }
                 };
             }
 
@@ -692,27 +625,27 @@ public class VaadinClient extends com.va
         };
     }
 
-    private FeaturesPanel createFeaturesPanel(User user) {
-        return new FeaturesPanel(m_associations, this) {
+    private DistributionsPanel createDistributionsPanel(User user) {
+        return new DistributionsPanel(m_associations, this) {
             @Override
             protected EditWindow createEditor(final NamedObject object, final List<UIExtensionFactory> extensions) {
-                return new EditWindow("Edit Feature", object, extensions) {
+                return new EditWindow("Edit Distribution", object, extensions) {
                     @Override
-                    protected void onOk(String name, String description) throws Exception {
-                        object.setDescription(description);
+                    protected void handleError(Exception e) {
+                        getWindow().showNotification("Failed to edit distribution!", "<br/>Reason: " + e.getMessage(),
+                            Notification.TYPE_ERROR_MESSAGE);
                     }
 
                     @Override
-                    protected void handleError(Exception e) {
-                        getWindow().showNotification("Failed to edit feature!", "<br/>Reason: " + e.getMessage(),
-                            Notification.TYPE_ERROR_MESSAGE);
+                    protected void onOk(String name, String description) throws Exception {
+                        object.setDescription(description);
                     }
                 };
             }
 
             @Override
-            protected FeatureRepository getRepository() {
-                return m_featureRepository;
+            protected DistributionRepository getRepository() {
+                return m_distributionRepository;
             }
 
             @Override
@@ -722,27 +655,27 @@ public class VaadinClient extends com.va
         };
     }
 
-    private DistributionsPanel createDistributionsPanel(User user) {
-        return new DistributionsPanel(m_associations, this) {
+    private FeaturesPanel createFeaturesPanel(User user) {
+        return new FeaturesPanel(m_associations, this) {
             @Override
             protected EditWindow createEditor(final NamedObject object, final List<UIExtensionFactory> extensions) {
-                return new EditWindow("Edit Distribution", object, extensions) {
+                return new EditWindow("Edit Feature", object, extensions) {
                     @Override
-                    protected void onOk(String name, String description) throws Exception {
-                        object.setDescription(description);
+                    protected void handleError(Exception e) {
+                        getWindow().showNotification("Failed to edit feature!", "<br/>Reason: " + e.getMessage(),
+                            Notification.TYPE_ERROR_MESSAGE);
                     }
 
                     @Override
-                    protected void handleError(Exception e) {
-                        getWindow().showNotification("Failed to edit distribution!", "<br/>Reason: " + e.getMessage(),
-                            Notification.TYPE_ERROR_MESSAGE);
+                    protected void onOk(String name, String description) throws Exception {
+                        object.setDescription(description);
                     }
                 };
             }
 
             @Override
-            protected DistributionRepository getRepository() {
-                return m_distributionRepository;
+            protected FeatureRepository getRepository() {
+                return m_featureRepository;
             }
 
             @Override
@@ -758,11 +691,6 @@ public class VaadinClient extends com.va
             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);
@@ -776,6 +704,11 @@ public class VaadinClient extends com.va
 
                         super.initDialog(object, factories);
                     }
+
+                    @Override
+                    protected void onOk(String name, String description) throws Exception {
+                        // Nothing to edit!
+                    }
                 };
             }
 
@@ -791,259 +724,271 @@ public class VaadinClient extends com.va
         };
     }
 
-    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
-                }
+    private GridLayout createToolbar(User user) {
+        final boolean showLogoutButton = m_useAuth;
+        final DependencyManager manager = m_manager;
+        MainActionToolbar mainActionToolbar = new MainActionToolbar(user, manager, showLogoutButton) {
+            @Override
+            protected void doAfterCommit() throws IOException {
+                updateTableData();
+
+                m_statusLine.setStatus("Local changes committed...");
             }
-        }
 
-        public AcceptCriterion getAcceptCriterion() {
-            return new Or(VerticalLocationIs.MIDDLE);
-        }
+            @Override
+            protected void doAfterLogout() throws IOException {
+                // Close the application and reload the main window...
+                close();
+            }
 
-        protected abstract void associateFromLeft(String left, String right);
+            @Override
+            protected void doAfterRetrieve() throws IOException {
+                updateTableData();
 
-        protected abstract void associateFromRight(String left, String right);
-    }
+                m_statusLine.setStatus("Repositories updated...");
+            }
 
-    /**
-     * Create a button to show a pop window for adding new features.
-     * 
-     * @param user
-     * 
-     * @param main
-     *            Main Window
-     * @return Button
-     */
-    private Button createAddArtifactButton(User user) {
-        Button button = new Button("Add artifact...");
-        button.addListener(new Button.ClickListener() {
-            public void buttonClick(ClickEvent event) {
-                showAddArtifactDialog();
+            @Override
+            protected void doAfterRevert() throws IOException {
+                updateTableData();
+
+                m_statusLine.setStatus("Local changes reverted...");
             }
-        });
-        return button;
+
+            @Override
+            protected RepositoryAdmin getRepositoryAdmin() {
+                return m_admin;
+            }
+
+            private void updateTableData() {
+                m_artifactsPanel.populate();
+                m_featuresPanel.populate();
+                m_distributionsPanel.populate();
+                m_targetsPanel.populate();
+            }
+        };
+        return mainActionToolbar;
     }
 
-    /***
-     * Create a button to show popup window for adding a new feature. On success this calls the createFeature() method.
-     * 
-     * @param user
-     * 
-     * @return the add-feature button instance.
-     */
-    private Button createAddFeatureButton(User user) {
-        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);
-                    }
+    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_mainToolbar = createToolbar(user);
+        m_grid.addComponent(m_mainToolbar, 0, 0, count - 1, 0);
+
+        m_artifactsPanel = createArtifactsPanel(user);
 
-                    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());
+        final GenericUploadHandler uploadHandler = new GenericUploadHandler(m_sessionDir) {
+            @Override
+            public void updateProgress(long readBytes, long contentLength) {
+                Float percentage = new Float(readBytes / (float) contentLength);
+                m_progress.setValue(percentage);
             }
-        });
-        return button;
-    }
 
-    /**
-     * Create a button to show a popup window for adding a new distribution. On success this calls the
-     * createDistribution() method.
-     * 
-     * @param user
-     * 
-     * @return the add-distribution button instance.
-     */
-    private Button createAddDistributionButton(User user) {
-        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);
-                    }
+            @Override
+            protected void artifactsUploaded(List<UploadHandle> uploadedArtifacts) {
+                StringBuilder failedMsg = new StringBuilder();
+                StringBuilder successMsg = new StringBuilder();
+                Set<String> selection = new HashSet<String>();
 
-                    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);
+                for (UploadHandle handle : uploadedArtifacts) {
+                    if (!handle.isSuccessful()) {
+                        // Upload failed, so let's report this one...
+                        appendFailure(failedMsg, handle);
+
+                        m_log.log(LogService.LOG_ERROR, "Upload of " + handle.getFile() + " failed.", handle.getFailureReason());
                     }
-                };
-                window.show(getMainWindow());
-            }
-        });
+                    else {
+                        try {
+                            // Upload was successful, try to upload it to our OBR...
+                            ArtifactObject artifact = uploadToOBR(handle);
+                            if (artifact != null) {
+                                selection.add(artifact.getDefinition());
 
-        return button;
-    }
+                                appendSuccess(successMsg, handle);
+                            }
+                        }
+                        catch (ArtifactAlreadyExistsException exception) {
+                            appendFailureExists(failedMsg, handle);
 
-    /**
-     * Create a button to show a popup window for adding a new target. On success this calls the createTarget() method
-     * 
-     * @param user
-     * 
-     * @return the add-target button instance.
-     */
-    private Button createAddTargetButton(User user) {
-        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);
-                    }
+                            m_log.log(LogService.LOG_WARNING, "Upload of " + handle.getFilename() + " failed, as it already exists!");
+                        }
+                        catch (Exception exception) {
+                            appendFailure(failedMsg, handle, exception);
 
-                    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);
+                            m_log.log(LogService.LOG_ERROR, "Upload of " + handle.getFilename() + " failed.", exception);
+                        }
                     }
 
-                    @Override
-                    protected void initDialog() {
-                        m_name.setCaption("Identifier");
-                        m_description.setVisible(false);
+                    // We're done with this (temporary) file, so we can remove it...
+                    handle.cleanup();
+                }
 
-                        super.initDialog();
-                    }
-                };
-                window.show(getMainWindow());
-            }
-        });
-        return button;
-    }
+                m_artifactsPanel.setValue(selection);
 
-    /**
-     * 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);
-    }
+                // Notify the user what the overall status was...
+                Notification notification = createNotification(failedMsg, successMsg);
+                getMainWindow().showNotification(notification);
 
-    /**
-     * 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);
-    }
+                m_progress.setStyleName("invisible");
+                m_statusLine.setStatus(notification.getCaption() + "...");
+            }
 
-    /**
-     * 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);
-    }
+            @Override
+            protected void uploadStarted(UploadHandle upload) {
+                m_progress.setStyleName("visible");
+                m_progress.setValue(new Float(0.0f));
 
-    private ArtifactObject getArtifact(String definition) {
-        return m_artifactRepository.get(definition);
-    }
+                m_statusLine.setStatus("Upload of '%s' started...", upload.getFilename());
+            }
 
-    private FeatureObject getFeature(String name) {
-        return m_featureRepository.get(name);
-    }
+            private void appendFailure(StringBuilder sb, UploadHandle handle) {
+                appendFailure(sb, handle, handle.getFailureReason());
+            }
 
-    private DistributionObject getDistribution(String name) {
-        return m_distributionRepository.get(name);
-    }
+            private void appendFailure(StringBuilder sb, UploadHandle handle, Exception cause) {
+                sb.append("<li>'").append(handle.getFile().getName()).append("': failed");
+                if (cause != null) {
+                    sb.append(", possible reason:<br/>").append(cause.getMessage());
+                }
+                sb.append("</li>");
+            }
 
-    private StatefulTargetObject getTarget(String name) {
-        return m_statefulTargetRepository.get(name);
-    }
+            private void appendFailureExists(StringBuilder sb, UploadHandle handle) {
+                sb.append("<li>'").append(handle.getFile().getName()).append("': already exists in repository</li>");
+            }
 
-    private void showAddArtifactDialog() {
-        final AddArtifactWindow window = new AddArtifactWindow(m_sessionDir, m_obrUrl, m_repositoryXML) {
-            @Override
-            protected ArtifactRepository getArtifactRepository() {
-                return m_artifactRepository;
+            private void appendSuccess(StringBuilder sb, UploadHandle handle) {
+                sb.append("<li>'").append(handle.getFile().getName()).append("': added to repository</li>");
             }
 
-            @Override
-            protected URLConnection openConnection(URL url) throws IOException {
-                return m_connectionFactory.createConnection(url);
+            private Notification createNotification(StringBuilder failedMsg, StringBuilder successMsg) {
+                String caption = "Upload completed";
+                int delay = 500; // msec.
+                StringBuilder notification = new StringBuilder();
+                if (failedMsg.length() > 0) {
+                    caption = "Upload completed with failures";
+                    delay = -1;
+                    notification.append("<ul>").append(failedMsg).append("</ul>");
+                }
+                if (successMsg.length() > 0) {
+                    notification.append("<ul>").append(successMsg).append("</ul>");
+                }
+                if (delay < 0) {
+                    notification.append("<p>(click to dismiss this notification).</p>");
+                }
+
+                Notification summary = new Notification(caption, notification.toString(), Notification.TYPE_TRAY_NOTIFICATION);
+                summary.setDelayMsec(delay);
+                return summary;
             }
 
-            @Override
-            protected LogService getLogger() {
-                return m_log;
+            private ArtifactObject uploadToOBR(UploadHandle handle) throws IOException {
+                return UploadHelper.importRemoteBundle(m_artifactRepository, handle.getFile());
             }
         };
 
-        // Open the subwindow by adding it to the parent window
-        window.showWindow(getMainWindow());
+        final DragAndDropWrapper finalUploadedArtifacts = new DragAndDropWrapper(m_artifactsPanel);
+        finalUploadedArtifacts.setCaption(m_artifactsPanel.getCaption());
+        finalUploadedArtifacts.setSizeFull();
+        finalUploadedArtifacts.setDropHandler(new ArtifactDropHandler(uploadHandler));
+
+        m_artifactToolbar = new HorizontalLayout();
+        m_artifactToolbar.addComponent(createAddArtifactButton(user));
+
+        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(finalUploadedArtifacts, count, 2);
+            m_grid.addComponent(m_artifactToolbar, count, 1);
+            count++;
+        }
+
+        m_featuresPanel = createFeaturesPanel(user);
+        m_featureToolbar = createAddFeatureButton(user);
+
+        if (auth.hasRole("viewFeature")) {
+            m_grid.addComponent(m_featuresPanel, count, 2);
+            m_grid.addComponent(m_featureToolbar, count, 1);
+            count++;
+        }
+
+        m_distributionsPanel = createDistributionsPanel(user);
+        m_distributionToolbar = createAddDistributionButton(user);
+
+        if (auth.hasRole("viewDistribution")) {
+            m_grid.addComponent(m_distributionsPanel, count, 2);
+            m_grid.addComponent(m_distributionToolbar, count, 1);
+            count++;
+        }
+
+        m_targetsPanel = createTargetsPanel(user);
+        m_targetToolbar = createAddTargetButton(user);
+
+        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.setAssociatedTables(null, m_featuresPanel);
+        m_featuresPanel.setAssociatedTables(m_artifactsPanel, m_distributionsPanel);
+        m_distributionsPanel.setAssociatedTables(m_featuresPanel, m_targetsPanel);
+        m_targetsPanel.setAssociatedTables(m_distributionsPanel, null);
+
+        m_grid.setRowExpandRatio(2, 1.0f);
+
+        m_statusLine = new StatusLine();
+
+        m_grid.addComponent(m_statusLine, 0, 3, 2, 3);
+
+        m_progress = new ProgressIndicator(0f);
+        m_progress.setStyleName("invisible");
+        m_progress.setIndeterminate(false);
+        m_progress.setPollingInterval(1000);
+
+        m_grid.addComponent(m_progress, 3, 3);
+
+        m_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 }));
+
+        addListener(m_statusLine, RepositoryObject.PUBLIC_TOPIC_ROOT.concat(RepositoryObject.TOPIC_ALL_SUFFIX));
+        addListener(m_mainToolbar, RepositoryAdmin.TOPIC_STATUSCHANGED, RepositoryAdmin.TOPIC_LOGIN, RepositoryAdmin.TOPIC_REFRESH);
+        addListener(m_artifactsPanel, ArtifactObject.TOPIC_ALL, RepositoryAdmin.TOPIC_STATUSCHANGED, RepositoryAdmin.TOPIC_LOGIN, RepositoryAdmin.TOPIC_REFRESH);
+        addListener(m_featuresPanel, FeatureObject.TOPIC_ALL, RepositoryAdmin.TOPIC_STATUSCHANGED, RepositoryAdmin.TOPIC_LOGIN, RepositoryAdmin.TOPIC_REFRESH);
+        addListener(m_distributionsPanel, DistributionObject.TOPIC_ALL, RepositoryAdmin.TOPIC_STATUSCHANGED, RepositoryAdmin.TOPIC_LOGIN, RepositoryAdmin.TOPIC_REFRESH);
+        addListener(m_targetsPanel, StatefulTargetObject.TOPIC_ALL, TargetObject.TOPIC_ALL, RepositoryAdmin.TOPIC_STATUSCHANGED, RepositoryAdmin.TOPIC_LOGIN, RepositoryAdmin.TOPIC_REFRESH);
+
+        m_mainWindow.addComponent(m_grid);
     }
 
     /**
@@ -1090,4 +1035,46 @@ public class VaadinClient extends com.va
             return false;
         }
     }
+
+    /**
+     * @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 showAddArtifactDialog() {
+        final AddArtifactWindow window = new AddArtifactWindow(m_sessionDir, m_obrUrl, m_repositoryXML) {
+            @Override
+            protected ArtifactRepository getArtifactRepository() {
+                return m_artifactRepository;
+            }
+
+            @Override
+            protected ConnectionFactory getConnectionFactory() {
+                return m_connectionFactory;
+            }
+
+            @Override
+            protected LogService getLogger() {
+                return m_log;
+            }
+        };
+
+        // Open the subwindow by adding it to the parent window
+        window.showWindow(getMainWindow());
+    }
+
+    /**
+     * 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();
+    }
 }

Modified: ace/trunk/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/component/ArtifactsPanel.java
URL: http://svn.apache.org/viewvc/ace/trunk/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/component/ArtifactsPanel.java?rev=1536411&r1=1536410&r2=1536411&view=diff
==============================================================================
--- ace/trunk/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/component/ArtifactsPanel.java (original)
+++ ace/trunk/org.apache.ace.webui.vaadin/src/org/apache/ace/webui/vaadin/component/ArtifactsPanel.java Mon Oct 28 17:03:53 2013
@@ -28,7 +28,7 @@ import org.apache.ace.client.repository.
 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.AssociationManager;
 import org.osgi.framework.Constants;
 
 import com.vaadin.data.Item;
@@ -36,83 +36,36 @@ import com.vaadin.data.Item;
 /**
  * Provides an object panel for displaying artifacts.
  */
-public abstract class ArtifactsPanel extends BaseObjectPanel<ArtifactObject, ArtifactRepository> {
+public abstract class ArtifactsPanel extends BaseObjectPanel<ArtifactObject, ArtifactRepository, RepositoryObject, FeatureObject> {
 
     /**
      * Creates a new {@link ArtifactsPanel} instance.
      * 
      * @param associations
      *            the assocation-holder object;
-     * @param associationRemover
-     *            the helper for removing associations.
+     * @param associationMgr
+     *            the helper for creating/removing associations.
      */
-    public ArtifactsPanel(AssociationHelper associations, AssociationRemover associationRemover) {
-        super(associations, associationRemover, "Artifact", UIExtensionFactory.EXTENSION_POINT_VALUE_ARTIFACT, true);
+    public ArtifactsPanel(AssociationHelper associations, AssociationManager associationMgr) {
+        super(associations, associationMgr, "Artifact", UIExtensionFactory.EXTENSION_POINT_VALUE_ARTIFACT, true);
     }
 
     @Override
-    protected void add(ArtifactObject artifact) {
-        String itemId = artifact.getDefinition();
-        String parentId = getParentId(artifact);
-
-        if (parentId != null && !containsId(parentId)) {
-            Item item = addItem(parentId);
-            item.getItemProperty(OBJECT_NAME).setValue(getParentDisplayName(artifact));
-            item.getItemProperty(OBJECT_DESCRIPTION).setValue("");
-            // we *must* set a non-null icon for the parent as well to ensure that the tree-table open/collapse icon is
-            // rendered properly...
-            setItemIcon(parentId, createIconResource("resource_workingstate_unchanged"));
-        }
-
-        Item item = addItem(itemId);
-        if (item != null) {
-            populateItem(artifact, item);
-        }
-
-        if (parentId != null) {
-            setParent(itemId, parentId);
-            setCollapsed(parentId, false);
-            setItemIcon(artifact);
-        }
-
-        setChildrenAllowed(itemId, false);
+    protected boolean doCreateRightSideAssociation(ArtifactObject artifact, FeatureObject feature) {
+        m_associationManager.createArtifact2FeatureAssociation(artifact, feature);
+        return true;
     }
 
     @Override
-    protected boolean doRemoveRightSideAssociation(ArtifactObject object, RepositoryObject other) {
-        List<Artifact2FeatureAssociation> associations = object.getAssociationsWith((FeatureObject) other);
+    protected boolean doRemoveRightSideAssociation(ArtifactObject object, FeatureObject other) {
+        List<Artifact2FeatureAssociation> associations = object.getAssociationsWith(other);
         for (Artifact2FeatureAssociation association : associations) {
-            m_associationRemover.removeAssociation(association);
+            m_associationManager.removeAssociation(association);
         }
         return true;
     }
 
-    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);
-        }
-    }
-
-    @Override
-    protected boolean isSupportedEntity(RepositoryObject entity) {
-        return (entity instanceof ArtifactObject) && !isResourceProcessor((ArtifactObject) entity);
-    }
-
     @Override
-    protected void populateItem(ArtifactObject artifact, Item item) {
-        item.getItemProperty(OBJECT_NAME).setValue(getDisplayName(artifact));
-        item.getItemProperty(OBJECT_DESCRIPTION).setValue(artifact.getDescription());
-        item.getItemProperty(ACTION_UNLINK).setValue(createUnlinkButton(artifact));
-        item.getItemProperty(ACTION_DELETE).setValue(createRemoveItemButton(artifact));
-    }
-
     protected String getDisplayName(ArtifactObject artifact) {
         String bv = artifact.getAttribute(Constants.BUNDLE_VERSION);
         if (bv != null) {
@@ -121,7 +74,8 @@ public abstract class ArtifactsPanel ext
         return artifact.getName();
     }
 
-    private String getParentDisplayName(ArtifactObject artifact) {
+    @Override
+    protected String getParentDisplayName(ArtifactObject artifact) {
         String bn = artifact.getAttribute(Constants.BUNDLE_NAME);
         if (bn != null) {
             return bn;
@@ -140,7 +94,8 @@ public abstract class ArtifactsPanel ext
         return name;
     }
 
-    private String getParentId(ArtifactObject artifact) {
+    @Override
+    protected String getParentId(ArtifactObject artifact) {
         String bsn = artifact.getAttribute(Constants.BUNDLE_SYMBOLICNAME);
         if (bsn != null) {
             return bsn;
@@ -149,6 +104,32 @@ public abstract class ArtifactsPanel ext
         return null;
     }
 
+    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);
+        }
+    }
+
+    @Override
+    protected boolean isSupportedEntity(RepositoryObject entity) {
+        return (entity instanceof ArtifactObject) && !isResourceProcessor((ArtifactObject) entity);
+    }
+
+    @Override
+    protected void populateItem(ArtifactObject artifact, Item item) {
+        item.getItemProperty(OBJECT_NAME).setValue(getDisplayName(artifact));
+        item.getItemProperty(OBJECT_DESCRIPTION).setValue(artifact.getDescription());
+        item.getItemProperty(ACTION_UNLINK).setValue(new RemoveLinkButton(artifact));
+        item.getItemProperty(ACTION_DELETE).setValue(new RemoveItemButton(artifact));
+    }
+
     /**
      * Returns whether or not the given artifact is actually a resource processor.
      *