diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/DataSetMetadataPanel.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/DataSetMetadataPanel.java
index d0fefe37d8a0298791c390b784c6b399f54c406a..a9be0c569a79a55e38de8f052cb7d6101387fbbe 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/DataSetMetadataPanel.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/DataSetMetadataPanel.java
@@ -144,7 +144,11 @@ public class DataSetMetadataPanel extends JPanel implements Observer
 
     private final JLabel ownerIdLabel;
 
-    private final JTextField ownerIdText;
+    private final JTextField sampleIdText;
+
+    private final ExperimentPickerPanel experimentPicker;
+
+    private final JPanel ownerIdPanel;
 
     private final ButtonGroup ownerButtonGroup;
 
@@ -186,13 +190,20 @@ public class DataSetMetadataPanel extends JPanel implements Observer
 
         // Initialize the fields in the gui
         ownerIdLabel = new JLabel("Owner:", JLabel.TRAILING);
-        ownerIdText = new JTextField();
+        sampleIdText = new JTextField();
+        experimentPicker = new ExperimentPickerPanel(mainWindow, clientModel.getExperiments());
+
+        ownerIdPanel = new JPanel(new CardLayout());
+
         ownerButtonGroup = new ButtonGroup();
         experimentButton = new JRadioButton("Experiment");
         sampleButton = new JRadioButton("Sample");
         ownerButtonGroup.add(experimentButton);
         ownerButtonGroup.add(sampleButton);
 
+        ownerIdPanel.add(experimentPicker, experimentButton.getText());
+        ownerIdPanel.add(sampleIdText, sampleButton.getText());
+
         dataSetTypeComboBox = new JComboBox();
         dataSetTypePanel = new JPanel();
 
@@ -218,7 +229,8 @@ public class DataSetMetadataPanel extends JPanel implements Observer
     {
         if (null == newDataSetInfo)
         {
-            ownerIdText.setText(EMPTY_FILE_SELECTION);
+            sampleIdText.setText(EMPTY_FILE_SELECTION);
+            experimentPicker.setText(EMPTY_FILE_SELECTION);
             updateFileLabel();
             disableAllWidgets();
             return;
@@ -227,14 +239,15 @@ public class DataSetMetadataPanel extends JPanel implements Observer
         enableAllWidgets();
 
         NewDataSetDTOBuilder builder = newDataSetInfo.getNewDataSetBuilder();
-        ownerIdText.setText(builder.getDataSetOwnerIdentifier());
         switch (builder.getDataSetOwnerType())
         {
             case EXPERIMENT:
                 ownerButtonGroup.setSelected(experimentButton.getModel(), true);
+                experimentPicker.setText(builder.getDataSetOwnerIdentifier());
                 break;
             case SAMPLE:
                 ownerButtonGroup.setSelected(sampleButton.getModel(), true);
+                sampleIdText.setText(builder.getDataSetOwnerIdentifier());
                 break;
         }
 
@@ -274,10 +287,12 @@ public class DataSetMetadataPanel extends JPanel implements Observer
     private ArrayList<JComponent> getAllEditableWidgets()
     {
         ArrayList<JComponent> editableWidgets = new ArrayList<JComponent>();
-        editableWidgets.add(ownerIdText);
+        editableWidgets.add(sampleIdText);
+        editableWidgets.add(experimentPicker);
         editableWidgets.add(dataSetFileButton);
         editableWidgets.add(experimentButton);
         editableWidgets.add(sampleButton);
+        editableWidgets.add(experimentPicker);
         editableWidgets.add(dataSetTypeComboBox);
 
         for (DataSetPropertiesPanel panel : propertiesPanels.values())
@@ -367,22 +382,44 @@ public class DataSetMetadataPanel extends JPanel implements Observer
         // The owner row
         ownerIdLabel.setPreferredSize(new Dimension(LABEL_WIDTH, BUTTON_HEIGHT));
 
-        ownerIdText.setPreferredSize(new Dimension(BUTTON_WIDTH, BUTTON_HEIGHT));
-        ownerIdText.addActionListener(new ActionListener()
+        sampleIdText.setPreferredSize(new Dimension(BUTTON_WIDTH, BUTTON_HEIGHT));
+        sampleIdText.addActionListener(new ActionListener()
             {
 
                 public void actionPerformed(ActionEvent e)
                 {
-                    setOwnerId(ownerIdText.getText());
+                    setOwnerId(sampleIdText.getText());
                 }
 
             });
-        ownerIdText.addFocusListener(new FocusListener()
+        sampleIdText.addFocusListener(new FocusListener()
             {
 
                 public void focusLost(FocusEvent e)
                 {
-                    setOwnerId(ownerIdText.getText());
+                    setOwnerId(sampleIdText.getText());
+                }
+
+                public void focusGained(FocusEvent e)
+                {
+                    // Do nothing
+                }
+            });
+
+        experimentPicker.setPreferredSize(new Dimension(BUTTON_WIDTH, BUTTON_HEIGHT));
+        experimentPicker.addActionListener(new ActionListener()
+            {
+                public void actionPerformed(ActionEvent e)
+                {
+                    setOwnerId(experimentPicker.getText());
+                }
+
+            });
+        experimentPicker.addFocusListener(new FocusListener()
+            {
+                public void focusLost(FocusEvent e)
+                {
+                    setOwnerId(experimentPicker.getText());
                 }
 
                 public void focusGained(FocusEvent e)
@@ -396,7 +433,9 @@ public class DataSetMetadataPanel extends JPanel implements Observer
                 public void actionPerformed(ActionEvent e)
                 {
                     setOwnerType(DataSetOwnerType.EXPERIMENT);
-
+                    setOwnerId(experimentPicker.getText());
+                    CardLayout cardLayout = (CardLayout) ownerIdPanel.getLayout();
+                    cardLayout.show(ownerIdPanel, experimentButton.getText());
                 }
             });
         experimentButton.setSelected(true);
@@ -405,10 +444,13 @@ public class DataSetMetadataPanel extends JPanel implements Observer
                 public void actionPerformed(ActionEvent e)
                 {
                     setOwnerType(DataSetOwnerType.SAMPLE);
+                    setOwnerId(sampleIdText.getText());
+                    CardLayout cardLayout = (CardLayout) ownerIdPanel.getLayout();
+                    cardLayout.show(ownerIdPanel, sampleButton.getText());
                 }
             });
 
-        addRow(2, ownerIdLabel, ownerIdText, ownerButtonGroup);
+        addRow(2, ownerIdLabel, ownerIdPanel, ownerButtonGroup);
 
         // The data set type row
         JLabel label = new JLabel("Data Set Type:", JLabel.TRAILING);
@@ -628,7 +670,7 @@ public class DataSetMetadataPanel extends JPanel implements Observer
     public synchronized void syncErrors()
     {
         // Clear all errors first
-        clearError(ownerIdLabel, ownerIdText, null);
+        clearError(ownerIdLabel, ownerIdPanel, null);
         clearError(dataSetFileLabel, dataSetFileComboBox, validationErrors);
 
         List<ValidationError> errors = newDataSetInfo.getValidationErrors();
@@ -637,7 +679,7 @@ public class DataSetMetadataPanel extends JPanel implements Observer
             switch (error.getTarget())
             {
                 case DATA_SET_OWNER:
-                    displayError(ownerIdLabel, ownerIdText, validationErrors, error);
+                    displayError(ownerIdLabel, ownerIdPanel, validationErrors, error);
                     break;
 
                 case DATA_SET_TYPE:
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/DataSetUploadClientModel.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/DataSetUploadClientModel.java
index 8a27b1bb821ebb0feb82a21f967d662bd5d5ff89..8271f79d3d1a31c8de9bbd75abc65187d707f7d5 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/DataSetUploadClientModel.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/DataSetUploadClientModel.java
@@ -40,11 +40,14 @@ import ch.systemsx.cisd.openbis.dss.generic.shared.api.v1.NewDataSetMetadataDTO;
 import ch.systemsx.cisd.openbis.dss.generic.shared.api.v1.validation.ValidationError;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.ControlledVocabularyPropertyType.VocabularyTerm;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSetType;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Experiment;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Project;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.PropertyType;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.PropertyTypeGroup;
 import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataTypeCode;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.Vocabulary;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ProjectIdentifier;
 import ch.systemsx.cisd.openbis.generic.shared.util.SimplePropertyValidator;
 
 /**
@@ -84,12 +87,25 @@ public class DataSetUploadClientModel
 
     private HashMap<Vocabulary, List<VocabularyTerm>> vocabularyTerms;
 
+    private List<Project> projects;
+
+    private List<Experiment> experiments;
+
     public DataSetUploadClientModel(DssCommunicationState commState, ITimeProvider timeProvider)
     {
         this.openBISService = commState.getOpenBISService();
         this.timeProvider = timeProvider;
         dataSetTypes = openBISService.listDataSetTypes();
         vocabularyTerms = openBISService.getVocabularyTermsMap();
+        projects = openBISService.listProjects();
+
+        List<String> projectIds = new ArrayList<String>();
+        for (Project project : projects)
+        {
+            ProjectIdentifier id = new ProjectIdentifier(project.getSpaceCode(), project.getCode());
+            projectIds.add(id.toString());
+        }
+        experiments = openBISService.listExperimentsForProjects(projectIds);
     }
 
     /**
@@ -513,8 +529,8 @@ public class DataSetUploadClientModel
     public void addUnofficialVocabularyTerm(Vocabulary vocabulary, String code, String label,
             String description, Long previousTermOrdinal)
     {
-        openBISService.addAdHocVocabularyTerm(TechId.create(vocabulary), code, label,
-                description, previousTermOrdinal);
+        openBISService.addAdHocVocabularyTerm(TechId.create(vocabulary), code, label, description,
+                previousTermOrdinal);
         dataSetTypes = openBISService.listDataSetTypes();
         vocabularyTerms = openBISService.getVocabularyTermsMap();
 
@@ -538,4 +554,9 @@ public class DataSetUploadClientModel
     {
         return vocabularyTerms.get(vocabulary);
     }
+
+    public List<Experiment> getExperiments()
+    {
+        return experiments;
+    }
 }
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/ExperimentPickerDialog.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/ExperimentPickerDialog.java
new file mode 100644
index 0000000000000000000000000000000000000000..14b30da99c6181d26f9bf397285f056282e3ed5a
--- /dev/null
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/ExperimentPickerDialog.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2011 ETH Zuerich, CISD
+ *
+ * Licensed 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 ch.systemsx.cisd.openbis.dss.client.api.gui;
+
+import java.awt.Dimension;
+import java.awt.Point;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JDialog;
+import javax.swing.JFrame;
+import javax.swing.JOptionPane;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.ListSelectionModel;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.table.JTableHeader;
+
+import ch.systemsx.cisd.openbis.dss.client.api.gui.table.SortButtonRenderer;
+import ch.systemsx.cisd.openbis.dss.client.api.gui.table.SortableFilterableTableModel;
+import ch.systemsx.cisd.openbis.dss.client.api.gui.table.TableHeaderMouseListener;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Experiment;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifier;
+import ch.systemsx.cisd.openbis.generic.shared.dto.identifier.ExperimentIdentifierFactory;
+
+/**
+ * @author Pawel Glyzewski
+ */
+public class ExperimentPickerDialog extends JDialog
+{
+    private static final long serialVersionUID = 6688336042860619854L;
+
+    private static String[] HEADERS = new String[]
+        { "Space code", "Project code", "Experiment code", "Experiment identifier" };
+
+    private final JFrame mainWindow;
+
+    private final JTable table;
+
+    final JTextField filterField;
+
+    private final JOptionPane optionPane;
+
+    public ExperimentPickerDialog(JFrame mainWindow, List<Experiment> experiments)
+    {
+        super(mainWindow, "Pick an experiment", true);
+
+        this.mainWindow = mainWindow;
+        table = createTable(prepareData(experiments));
+        filterField = createFilterField(table);
+        optionPane = createOptionPane(filterField, table, this);
+
+        this.setContentPane(optionPane);
+    }
+
+    private static JOptionPane createOptionPane(JTextField filterField, final JTable table,
+            final JDialog parent)
+    {
+        final JScrollPane scrollPane = new JScrollPane(table);
+
+        Object[] objects = new Object[]
+            { "Filter experiments: ", filterField, "Select Experiment:", scrollPane };
+        final JOptionPane optionPane =
+                new JOptionPane(objects, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION);
+        optionPane.addPropertyChangeListener(new PropertyChangeListener()
+            {
+                public void propertyChange(PropertyChangeEvent evt)
+                {
+                    if (evt.getPropertyName().equals(JOptionPane.VALUE_PROPERTY)
+                            && evt.getNewValue() != null)
+                    {
+                        if (((Integer) evt.getNewValue()).intValue() == JOptionPane.OK_OPTION
+                                && table.getSelectedRow() == -1)
+                        {
+                            JOptionPane.showMessageDialog(parent,
+                                    "Experiment needs to be selected!", "No experiment selected!",
+                                    JOptionPane.WARNING_MESSAGE);
+                            optionPane.setValue(optionPane.getInitialValue());
+                        } else
+                        {
+                            parent.setVisible(false);
+                        }
+                    }
+                }
+            });
+        return optionPane;
+    }
+
+    private static List<String[]> prepareData(List<Experiment> experiments)
+    {
+        List<String[]> data = new ArrayList<String[]>(experiments.size());
+        for (Experiment experiment : experiments)
+        {
+            ExperimentIdentifier expId =
+                    ExperimentIdentifierFactory.parse(experiment.getIdentifier());
+
+            data.add(new String[]
+                { expId.getSpaceCode(), expId.getProjectCode(), expId.getExperimentCode(),
+                        experiment.getIdentifier() });
+        }
+
+        return data;
+    }
+
+    private static JTable createTable(List<String[]> data)
+    {
+        final JTable table = new JTable(new SortableFilterableTableModel(data, HEADERS));
+
+        table.setPreferredScrollableViewportSize(new Dimension(500, 150));
+        table.setFillsViewportHeight(true);
+        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+
+        JTableHeader header = table.getTableHeader();
+        for (int i = 0; i < table.getColumnModel().getColumnCount() - 1; i++)
+        {
+            table.getColumnModel().getColumn(i).setPreferredWidth(110);
+        }
+        table.getColumnModel().getColumn(3).setPreferredWidth(200);
+
+        header.addMouseListener(new TableHeaderMouseListener((SortableFilterableTableModel) table
+                .getModel()));
+        header.setDefaultRenderer(new SortButtonRenderer());
+
+        return table;
+    }
+
+    private static JTextField createFilterField(final JTable table)
+    {
+        final JTextField filterField = new JTextField();
+        filterField.setEditable(true);
+        filterField.getDocument().addDocumentListener(new DocumentListener()
+            {
+                public void removeUpdate(DocumentEvent e)
+                {
+                    SortableFilterableTableModel model =
+                            (SortableFilterableTableModel) table.getModel();
+                    model.filter(filterField.getText());
+                }
+
+                public void insertUpdate(DocumentEvent e)
+                {
+                    SortableFilterableTableModel model =
+                            (SortableFilterableTableModel) table.getModel();
+                    model.filter(filterField.getText());
+                }
+
+                public void changedUpdate(DocumentEvent e)
+                {
+                    SortableFilterableTableModel model =
+                            (SortableFilterableTableModel) table.getModel();
+                    model.filter(filterField.getText());
+                }
+            });
+
+        return filterField;
+    }
+
+    public String pickExperiment()
+    {
+        this.pack();
+
+        int height = this.getHeight() > 500 ? 500 : this.getHeight();
+        int width = this.getWidth() > 600 ? 600 : this.getWidth();
+        this.setSize(width, height);
+
+        Point mwLocation = mainWindow.getLocationOnScreen();
+        int x = mwLocation.x + (mainWindow.getWidth() / 2) - (this.getWidth() / 2);
+        int y = mwLocation.y + (mainWindow.getHeight() / 2) - (this.getHeight() / 2);
+
+        this.setLocation(x > 0 ? x : 0, y > 0 ? y : 0);
+
+        this.setVisible(true);
+
+        Object value = optionPane.getValue();
+        optionPane.setValue(optionPane.getInitialValue());
+        if (value == null || ((Integer) value).intValue() == JOptionPane.CANCEL_OPTION)
+        {
+            return null;
+        } else
+        {
+            return table.getValueAt(table.getSelectedRow(), 3).toString();
+        }
+    }
+}
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/ExperimentPickerPanel.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/ExperimentPickerPanel.java
new file mode 100644
index 0000000000000000000000000000000000000000..41f8c71b5b2294f724eff6730bb839ef5ede16ee
--- /dev/null
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/ExperimentPickerPanel.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2011 ETH Zuerich, CISD
+ *
+ * Licensed 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 ch.systemsx.cisd.openbis.dss.client.api.gui;
+
+import java.awt.BorderLayout;
+import java.awt.Insets;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusListener;
+import java.util.List;
+
+import javax.swing.JButton;
+import javax.swing.JFrame;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
+
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Experiment;
+
+/**
+ * @author Pawel Glyzewski
+ */
+public class ExperimentPickerPanel extends JPanel
+{
+    private static final long serialVersionUID = -8093481985680332715L;
+
+    private final JTextField textField = new JTextField();
+
+    private final JButton button = new JButton("...");
+
+    private final ExperimentPickerDialog dialog;
+
+    public ExperimentPickerPanel(final JFrame mainWindow, final List<Experiment> experiments)
+    {
+        super(new BorderLayout());
+
+        dialog = new ExperimentPickerDialog(mainWindow, experiments);
+
+        button.setMargin(new Insets(button.getMargin().top, 2, button.getMargin().bottom, 2));
+
+        button.addActionListener(new ActionListener()
+            {
+                public void actionPerformed(ActionEvent e)
+                {
+                    String experimentId = dialog.pickExperiment();
+                    if (experimentId != null)
+                    {
+                        textField.setText(experimentId);
+                    }
+                }
+            });
+
+        add(textField, BorderLayout.CENTER);
+        add(button, BorderLayout.EAST);
+    }
+
+    public String getText()
+    {
+        return textField.getText();
+    }
+
+    public void setText(String text)
+    {
+        textField.setText(text);
+    }
+
+    public void addActionListener(ActionListener actionListener)
+    {
+        textField.addActionListener(actionListener);
+    }
+
+    @Override
+    public void addFocusListener(FocusListener focusListener)
+    {
+        textField.addFocusListener(focusListener);
+    }
+
+    @Override
+    public void setEnabled(boolean enabled)
+    {
+        textField.setEditable(enabled);
+        textField.setEnabled(enabled);
+        button.setEnabled(enabled);
+    }
+}
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/table/BevelArrowIcon.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/table/BevelArrowIcon.java
new file mode 100644
index 0000000000000000000000000000000000000000..2cf524f5057344f47eb06ea7e823f55fa8f0da57
--- /dev/null
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/table/BevelArrowIcon.java
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2011 ETH Zuerich, CISD
+ *
+ * Licensed 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 ch.systemsx.cisd.openbis.dss.client.api.gui.table;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+
+import javax.swing.Icon;
+import javax.swing.UIManager;
+
+/**
+ * @author Pawel Glyzewski
+ */
+public class BevelArrowIcon implements Icon
+{
+
+    /** Constant indicating that the arrow is pointing up. */
+    public static final int UP = 0;
+
+    /** Constant indicating that the arrow is pointing down. */
+    public static final int DOWN = 1;
+
+    /** The default arrow size. */
+    private static final int DEFAULT_SIZE = 11;
+
+    /** Edge color 1. */
+    private Color edge1;
+
+    /** Edge color 2. */
+    private Color edge2;
+
+    /** The fill color for the arrow icon. */
+    private Color fill;
+
+    /** The size of the icon. */
+    private int size;
+
+    /** The direction that the arrow is pointing (UP or DOWN). */
+    private int direction;
+
+    /**
+     * Standard constructor - builds an icon with the specified attributes.
+     * 
+     * @param direction .
+     * @param isRaisedView .
+     * @param isPressedView .
+     */
+    public BevelArrowIcon(final int direction, final boolean isRaisedView,
+            final boolean isPressedView)
+    {
+        if (isRaisedView)
+        {
+            if (isPressedView)
+            {
+                init(UIManager.getColor("controlLtHighlight"),
+                        UIManager.getColor("controlDkShadow"), UIManager.getColor("controlShadow"),
+                        DEFAULT_SIZE, direction);
+            } else
+            {
+                init(UIManager.getColor("controlHighlight"), UIManager.getColor("controlShadow"),
+                        UIManager.getColor("control"), DEFAULT_SIZE, direction);
+            }
+        } else
+        {
+            if (isPressedView)
+            {
+                init(UIManager.getColor("controlDkShadow"),
+                        UIManager.getColor("controlLtHighlight"),
+                        UIManager.getColor("controlShadow"), DEFAULT_SIZE, direction);
+            } else
+            {
+                init(UIManager.getColor("controlShadow"), UIManager.getColor("controlHighlight"),
+                        UIManager.getColor("control"), DEFAULT_SIZE, direction);
+            }
+        }
+    }
+
+    /**
+     * Standard constructor - builds an icon with the specified attributes.
+     * 
+     * @param edge1 the color of edge1.
+     * @param edge2 the color of edge2.
+     * @param fill the fill color.
+     * @param size the size of the arrow icon.
+     * @param direction the direction that the arrow points.
+     */
+    public BevelArrowIcon(final Color edge1, final Color edge2, final Color fill, final int size,
+            final int direction)
+    {
+        init(edge1, edge2, fill, size, direction);
+    }
+
+    /**
+     * Paints the icon at the specified position. Supports the Icon interface.
+     * 
+     * @param c .
+     * @param g .
+     * @param x .
+     * @param y .
+     */
+    public void paintIcon(final Component c, final Graphics g, final int x, final int y)
+    {
+        switch (this.direction)
+        {
+            case DOWN:
+                drawDownArrow(g, x, y);
+                break;
+            case UP:
+                drawUpArrow(g, x, y);
+                break;
+        }
+    }
+
+    /**
+     * Returns the width of the icon. Supports the Icon interface.
+     * 
+     * @return the icon width.
+     */
+    public int getIconWidth()
+    {
+        return this.size;
+    }
+
+    /**
+     * Returns the height of the icon. Supports the Icon interface.
+     * 
+     * @return the icon height.
+     */
+    public int getIconHeight()
+    {
+        return this.size;
+    }
+
+    /**
+     * Initialises the attributes of the arrow icon.
+     * 
+     * @param edge1 the color of edge1.
+     * @param edge2 the color of edge2.
+     * @param fill the fill color.
+     * @param size the size of the arrow icon.
+     * @param direction the direction that the arrow points.
+     */
+    @SuppressWarnings("hiding")
+    private void init(final Color edge1, final Color edge2, final Color fill, final int size,
+            final int direction)
+    {
+        this.edge1 = edge1;
+        this.edge2 = edge2;
+        this.fill = fill;
+        this.size = size;
+        this.direction = direction;
+    }
+
+    /**
+     * Draws the arrow pointing down.
+     * 
+     * @param g the graphics device.
+     * @param xo ??
+     * @param yo ??
+     */
+    private void drawDownArrow(final Graphics g, final int xo, final int yo)
+    {
+        g.setColor(this.edge1);
+        g.drawLine(xo, yo, xo + this.size - 1, yo);
+        g.drawLine(xo, yo + 1, xo + this.size - 3, yo + 1);
+        g.setColor(this.edge2);
+        g.drawLine(xo + this.size - 2, yo + 1, xo + this.size - 1, yo + 1);
+        int x = xo + 1;
+        int y = yo + 2;
+        int dx = this.size - 6;
+        while (y + 1 < yo + this.size)
+        {
+            g.setColor(this.edge1);
+            g.drawLine(x, y, x + 1, y);
+            g.drawLine(x, y + 1, x + 1, y + 1);
+            if (0 < dx)
+            {
+                g.setColor(this.fill);
+                g.drawLine(x + 2, y, x + 1 + dx, y);
+                g.drawLine(x + 2, y + 1, x + 1 + dx, y + 1);
+            }
+            g.setColor(this.edge2);
+            g.drawLine(x + dx + 2, y, x + dx + 3, y);
+            g.drawLine(x + dx + 2, y + 1, x + dx + 3, y + 1);
+            x += 1;
+            y += 2;
+            dx -= 2;
+        }
+        g.setColor(this.edge1);
+        g.drawLine(xo + (this.size / 2), yo + this.size - 1, xo + (this.size / 2), yo + this.size
+                - 1);
+    }
+
+    /**
+     * Draws the arrow pointing up.
+     * 
+     * @param g the graphics device.
+     * @param xo ??
+     * @param yo ??
+     */
+    private void drawUpArrow(final Graphics g, final int xo, final int yo)
+    {
+        g.setColor(this.edge1);
+        int x = xo + (this.size / 2);
+        g.drawLine(x, yo, x, yo);
+        x--;
+        int y = yo + 1;
+        int dx = 0;
+        while (y + 3 < yo + this.size)
+        {
+            g.setColor(this.edge1);
+            g.drawLine(x, y, x + 1, y);
+            g.drawLine(x, y + 1, x + 1, y + 1);
+            if (0 < dx)
+            {
+                g.setColor(this.fill);
+                g.drawLine(x + 2, y, x + 1 + dx, y);
+                g.drawLine(x + 2, y + 1, x + 1 + dx, y + 1);
+            }
+            g.setColor(this.edge2);
+            g.drawLine(x + dx + 2, y, x + dx + 3, y);
+            g.drawLine(x + dx + 2, y + 1, x + dx + 3, y + 1);
+            x -= 1;
+            y += 2;
+            dx += 2;
+        }
+        g.setColor(this.edge1);
+        g.drawLine(xo, yo + this.size - 3, xo + 1, yo + this.size - 3);
+        g.setColor(this.edge2);
+        g.drawLine(xo + 2, yo + this.size - 2, xo + this.size - 1, yo + this.size - 2);
+        g.drawLine(xo, yo + this.size - 1, xo + this.size, yo + this.size - 1);
+    }
+
+}
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/table/SortButtonRenderer.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/table/SortButtonRenderer.java
new file mode 100644
index 0000000000000000000000000000000000000000..4b22a969654a7023641100b0839b36be1b8be6b7
--- /dev/null
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/table/SortButtonRenderer.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright 2011 ETH Zuerich, CISD
+ *
+ * Licensed 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 ch.systemsx.cisd.openbis.dss.client.api.gui.table;
+
+import java.awt.Component;
+import java.awt.Insets;
+
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.SwingConstants;
+import javax.swing.UIManager;
+import javax.swing.border.Border;
+import javax.swing.table.JTableHeader;
+import javax.swing.table.TableCellRenderer;
+
+/**
+ * A table cell renderer for table headings - uses one of three JButton instances to indicate the
+ * sort order for the table column.
+ * <P>
+ * This class (and also BevelArrowIcon) is adapted from original code by Nobuo Tamemasa (version
+ * 1.0, 26-Feb-1999) posted on www.codeguru.com.
+ * 
+ * @author Pawel Glyzewski
+ */
+public class SortButtonRenderer implements TableCellRenderer
+{
+
+    /**
+     * Useful constant indicating NO sorting.
+     */
+    public static final int NONE = 0;
+
+    /**
+     * Useful constant indicating ASCENDING (that is, arrow pointing down) sorting in the table.
+     */
+    public static final int DOWN = 1;
+
+    /**
+     * Useful constant indicating DESCENDING (that is, arrow pointing up) sorting in the table.
+     */
+    public static final int UP = 2;
+
+    /**
+     * The current pressed column (-1 for no column).
+     */
+    private int pressedColumn = -1;
+
+    /**
+     * The three buttons that are used to render the table header cells.
+     */
+    private JButton normalButton;
+
+    /**
+     * The three buttons that are used to render the table header cells.
+     */
+    private JButton ascendingButton;
+
+    /**
+     * The three buttons that are used to render the table header cells.
+     */
+    private JButton descendingButton;
+
+    /**
+     * Used to allow the class to work out whether to use the buttuns or labels. Labels are required
+     * when using the aqua look and feel cos the buttons won't fit.
+     */
+    private boolean useLabels;
+
+    /**
+     * The normal label (only used with MacOSX).
+     */
+    private JLabel normalLabel;
+
+    /**
+     * The ascending label (only used with MacOSX).
+     */
+    private JLabel ascendingLabel;
+
+    /**
+     * The descending label (only used with MacOSX).
+     */
+    private JLabel descendingLabel;
+
+    /**
+     * Creates a new button renderer.
+     */
+    public SortButtonRenderer()
+    {
+
+        this.pressedColumn = -1;
+        this.useLabels = UIManager.getLookAndFeel().getID().equals("Aqua");
+
+        final Border border = UIManager.getBorder("TableHeader.cellBorder");
+
+        if (this.useLabels)
+        {
+            this.normalLabel = new JLabel();
+            this.normalLabel.setHorizontalAlignment(SwingConstants.LEADING);
+
+            this.ascendingLabel = new JLabel();
+            this.ascendingLabel.setHorizontalAlignment(SwingConstants.LEADING);
+            this.ascendingLabel.setHorizontalTextPosition(SwingConstants.LEFT);
+            this.ascendingLabel.setIcon(new BevelArrowIcon(BevelArrowIcon.DOWN, false, false));
+
+            this.descendingLabel = new JLabel();
+            this.descendingLabel.setHorizontalAlignment(SwingConstants.LEADING);
+            this.descendingLabel.setHorizontalTextPosition(SwingConstants.LEFT);
+            this.descendingLabel.setIcon(new BevelArrowIcon(BevelArrowIcon.UP, false, false));
+
+            this.normalLabel.setBorder(border);
+            this.ascendingLabel.setBorder(border);
+            this.descendingLabel.setBorder(border);
+        } else
+        {
+            this.normalButton = new JButton();
+            this.normalButton.setMargin(new Insets(0, 0, 0, 0));
+            this.normalButton.setHorizontalAlignment(SwingConstants.LEADING);
+
+            this.ascendingButton = new JButton();
+            this.ascendingButton.setMargin(new Insets(0, 0, 0, 0));
+            this.ascendingButton.setHorizontalAlignment(SwingConstants.LEADING);
+            this.ascendingButton.setHorizontalTextPosition(SwingConstants.LEFT);
+            this.ascendingButton.setIcon(new BevelArrowIcon(BevelArrowIcon.DOWN, false, false));
+            this.ascendingButton
+                    .setPressedIcon(new BevelArrowIcon(BevelArrowIcon.DOWN, false, true));
+
+            this.descendingButton = new JButton();
+            this.descendingButton.setMargin(new Insets(0, 0, 0, 0));
+            this.descendingButton.setHorizontalAlignment(SwingConstants.LEADING);
+            this.descendingButton.setHorizontalTextPosition(SwingConstants.LEFT);
+            this.descendingButton.setIcon(new BevelArrowIcon(BevelArrowIcon.UP, false, false));
+            this.descendingButton
+                    .setPressedIcon(new BevelArrowIcon(BevelArrowIcon.UP, false, true));
+
+            this.normalButton.setBorder(border);
+            this.ascendingButton.setBorder(border);
+            this.descendingButton.setBorder(border);
+        }
+    }
+
+    /**
+     * Returns the renderer component.
+     * 
+     * @param table the table.
+     * @param value the value.
+     * @param isSelected selected?
+     * @param hasFocus focussed?
+     * @param row the row.
+     * @param column the column.
+     * @return the renderer.
+     */
+    public Component getTableCellRendererComponent(final JTable table, final Object value,
+            final boolean isSelected, final boolean hasFocus, final int row, final int column)
+    {
+
+        if (table == null)
+        {
+            throw new NullPointerException("Table must not be null.");
+        }
+
+        final JComponent component;
+        final int cc = table.convertColumnIndexToModel(column);
+        final SortableFilterableTableModel model = (SortableFilterableTableModel) table.getModel();
+        final boolean isSorting = model.isSorting(column);
+        final boolean isAscending = model.isAscending(column);
+
+        final JTableHeader header = table.getTableHeader();
+        final boolean isPressed = (cc == this.pressedColumn);
+
+        if (this.useLabels)
+        {
+            final JLabel label = getRendererLabel(isSorting, isAscending);
+            label.setText((value == null) ? "" : value.toString());
+            component = label;
+        } else
+        {
+            final JButton button = getRendererButton(isSorting, isAscending);
+            button.setText((value == null) ? "" : value.toString());
+            button.getModel().setPressed(isPressed);
+            button.getModel().setArmed(isPressed);
+            component = button;
+        }
+
+        if (header != null)
+        {
+            component.setForeground(header.getForeground());
+            component.setBackground(header.getBackground());
+            component.setFont(header.getFont());
+        }
+
+        return component;
+    }
+
+    /**
+     * Returns the correct button component.
+     * 
+     * @param isSorting whether the render component represents the sort column.
+     * @param isAscending whether the model is ascending.
+     * @return either the ascending, descending or normal button.
+     */
+    private JButton getRendererButton(final boolean isSorting, final boolean isAscending)
+    {
+        if (isSorting)
+        {
+            if (isAscending)
+            {
+                return ascendingButton;
+            } else
+            {
+                return descendingButton;
+            }
+        } else
+        {
+            return normalButton;
+        }
+    }
+
+    /**
+     * Returns the correct label component.
+     * 
+     * @param isSorting whether the render component represents the sort column.
+     * @param isAscending whether the model is ascending.
+     * @return either the ascending, descending or normal label.
+     */
+    private JLabel getRendererLabel(final boolean isSorting, final boolean isAscending)
+    {
+        if (isSorting)
+        {
+            if (isAscending)
+            {
+                return ascendingLabel;
+            } else
+            {
+                return descendingLabel;
+            }
+        } else
+        {
+            return normalLabel;
+        }
+    }
+
+    /**
+     * Sets the pressed column.
+     * 
+     * @param column the column.
+     */
+    public void setPressedColumn(final int column)
+    {
+        this.pressedColumn = column;
+    }
+}
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/table/SortableFilterableTableModel.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/table/SortableFilterableTableModel.java
new file mode 100644
index 0000000000000000000000000000000000000000..daaaa9bd9bd795846d4f3418a5351444bc73f248
--- /dev/null
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/table/SortableFilterableTableModel.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2011 ETH Zuerich, CISD
+ *
+ * Licensed 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 ch.systemsx.cisd.openbis.dss.client.api.gui.table;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import javax.swing.table.AbstractTableModel;
+
+/**
+ * @author Pawel Glyzewski
+ */
+public class SortableFilterableTableModel extends AbstractTableModel
+{
+    private static final long serialVersionUID = -7363632554479395042L;
+
+    private static enum Direction
+    {
+        ASC, DESC
+    }
+
+    private static class RowComparator implements Comparator<String[]>
+    {
+        private final int columnNumber;
+
+        private final boolean reverse;
+
+        private RowComparator(int columnNumber, boolean reverse)
+        {
+            this.columnNumber = columnNumber;
+            this.reverse = reverse;
+        }
+
+        public int compare(String[] o1, String[] o2)
+        {
+            if (reverse)
+            {
+                return -o1[columnNumber].compareTo(o2[columnNumber]);
+
+            } else
+            {
+                return o1[columnNumber].compareTo(o2[columnNumber]);
+            }
+        }
+    }
+
+    private int sortByColumn = -1;
+
+    private Direction sortingOrder = Direction.ASC;
+
+    private final String[] headers;
+
+    private final List<String[]> data;
+
+    private List<String[]> viewData;
+
+    private String filter = "";
+
+    public SortableFilterableTableModel(List<String[]> data, String[] headers)
+    {
+        this.data = data;
+        this.headers = headers;
+    }
+
+    @Override
+    public String getColumnName(int col)
+    {
+        return headers[col];
+    }
+
+    public int getRowCount()
+    {
+        if (viewData != null)
+        {
+            return viewData.size();
+        } else
+        {
+            return data.size();
+        }
+    }
+
+    public int getColumnCount()
+    {
+        return headers.length;
+    }
+
+    public void filter(@SuppressWarnings("hiding") String filter)
+    {
+        this.filter = "(?i)" + filter;
+        Pattern pattern;
+
+        try
+        {
+            pattern = Pattern.compile(this.filter);
+        } catch (RuntimeException e)
+        {
+            pattern = Pattern.compile(".*");
+        }
+        viewData = new ArrayList<String[]>();
+        for (String[] row : data)
+        {
+            boolean matches = false;
+            for (String value : row)
+            {
+                matches = matches || pattern.matcher(value).find();
+                if (matches)
+                {
+                    viewData.add(row);
+                    break;
+                }
+            }
+        }
+
+        if (sortByColumn > -1)
+        {
+            Collections.sort(viewData, new RowComparator(sortByColumn,
+                    Direction.ASC != sortingOrder));
+        }
+
+        fireTableDataChanged();
+    }
+
+    public Object getValueAt(int rowIndex, int columnIndex)
+    {
+        if (viewData != null)
+        {
+            return viewData.get(rowIndex)[columnIndex];
+        } else
+        {
+            return data.get(rowIndex)[columnIndex];
+        }
+    }
+
+    public boolean isAscending(int column)
+    {
+        return Direction.ASC == sortingOrder;
+    }
+
+    public boolean isSorting(int column)
+    {
+        return column == sortByColumn;
+    }
+
+    public synchronized void toggleSort(int column)
+    {
+        if (column == sortByColumn)
+        {
+            switch (sortingOrder)
+            {
+                case ASC:
+                    sortingOrder = Direction.DESC;
+                    break;
+                case DESC:
+                    sortByColumn = -1;
+                    break;
+            }
+        } else
+        {
+            sortByColumn = column;
+            sortingOrder = Direction.ASC;
+        }
+
+        filter(filter);
+    }
+}
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/table/TableHeaderMouseListener.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/table/TableHeaderMouseListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..6be9443e7f879fae7d6f58ed987afee63aa96123
--- /dev/null
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/table/TableHeaderMouseListener.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2011 ETH Zuerich, CISD
+ *
+ * Licensed 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 ch.systemsx.cisd.openbis.dss.client.api.gui.table;
+
+import java.awt.Rectangle;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+
+import javax.swing.JTable;
+import javax.swing.table.JTableHeader;
+import javax.swing.table.TableColumnModel;
+
+/**
+ * @author Pawel Glyzewski
+ */
+public class TableHeaderMouseListener extends MouseAdapter
+{
+    private SortableFilterableTableModel model;
+
+    public TableHeaderMouseListener(SortableFilterableTableModel model)
+    {
+        this.model = model;
+    }
+
+    @Override
+    public void mouseClicked(MouseEvent evt)
+    {
+        JTable table = ((JTableHeader) evt.getSource()).getTable();
+        TableColumnModel colModel = table.getColumnModel();
+
+        // The index of the column whose header was clicked
+        int vColIndex = colModel.getColumnIndexAtX(evt.getX());
+        int mColIndex = table.convertColumnIndexToModel(vColIndex);
+
+        // Return if not clicked on any column header
+        if (vColIndex == -1)
+        {
+            return;
+        }
+
+        // Determine if mouse was clicked between column heads
+        Rectangle headerRect = table.getTableHeader().getHeaderRect(vColIndex);
+        if (vColIndex == 0)
+        {
+            headerRect.width -= 3; // Hard-coded constant
+        } else
+        {
+            headerRect.grow(-3, 0); // Hard-coded constant
+        }
+        if (headerRect.contains(evt.getX(), evt.getY()))
+        {
+            model.toggleSort(mColIndex);
+        }
+    }
+}
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/v1/ISimpleOpenbisServiceFacade.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/v1/ISimpleOpenbisServiceFacade.java
index fefc476f6290eed26510f7c02eddd67f90e98d89..2be32866619d8af9bc410bd66bce1ed1132794e0 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/v1/ISimpleOpenbisServiceFacade.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/v1/ISimpleOpenbisServiceFacade.java
@@ -25,6 +25,7 @@ import ch.systemsx.cisd.openbis.dss.generic.shared.api.v1.NewDataSetDTO;
 import ch.systemsx.cisd.openbis.dss.generic.shared.api.v1.validation.ValidationError;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSetType;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Experiment;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Project;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Sample;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SpaceWithProjectsAndRoleAssignments;
 
@@ -41,6 +42,12 @@ import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.SpaceWithProjectsAndRo
  */
 public interface ISimpleOpenbisServiceFacade
 {
+
+    /**
+     * Returns all available projects.
+     */
+    List<Project> listProjects();
+
     /**
      * Return all spaces enriched with their projects and role assignments.
      */
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/v1/impl/OpenbisServiceFacade.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/v1/impl/OpenbisServiceFacade.java
index d1f2a2828cb49c71f6d37daa024a7e6d92040aad..bb433c30f7a1e580c08dcaa7b9decc5095ce5e7a 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/v1/impl/OpenbisServiceFacade.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/v1/impl/OpenbisServiceFacade.java
@@ -151,6 +151,11 @@ public class OpenbisServiceFacade implements IOpenbisServiceFacade
     // ISimpleOpenbisServiceFacade
     //
 
+    public List<Project> listProjects()
+    {
+        return service.listProjects(sessionToken);
+    }
+
     public List<SpaceWithProjectsAndRoleAssignments> getSpacesWithProjects()
             throws EnvironmentFailureException
     {
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationService.java
index 91e99fb36faf489ead4849f356a62e854ee2811c..c24208e8157667b6f919ec74c0aa4d5e7f0749b2 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationService.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationService.java
@@ -163,7 +163,7 @@ public class GeneralInformationService extends AbstractServer<IGeneralInformatio
 
     public int getMinorVersion()
     {
-        return 9;
+        return 10;
     }
 
     private Map<String, List<RoleAssignmentPE>> getRoleAssignmentsPerSpace()
@@ -421,4 +421,11 @@ public class GeneralInformationService extends AbstractServer<IGeneralInformatio
 
         return Translator.translateExperiments(experiments);
     }
+
+    public List<Project> listProjects(String sessionToken)
+    {
+        checkSession(sessionToken);
+
+        return Translator.translateProjects(commonServer.listProjects(sessionToken));
+    }
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationServiceLogger.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationServiceLogger.java
index d983033ccf9ad93af35ba79489a4e8e3af089d2f..e054059f441e0f7f559ae1208aba77ba301cc557 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationServiceLogger.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/GeneralInformationServiceLogger.java
@@ -157,4 +157,9 @@ class GeneralInformationServiceLogger extends AbstractServerLogger implements
         return null;
     }
 
+    public List<Project> listProjects(String sessionToken)
+    {
+        logAccess(sessionToken, "list-projects");
+        return null;
+    }
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/Translator.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/Translator.java
index 2af9a350c4a49cc9358d10cee538475dc1148d56..0f9102c7e545578fd74593c343e18aae2fff7775 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/Translator.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/server/api/v1/Translator.java
@@ -36,6 +36,7 @@ import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSetType;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSetType.DataSetTypeInitializer;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Experiment;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Experiment.ExperimentInitializer;
+import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.Project;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.PropertyType;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.PropertyType.PropertyTypeInitializer;
 import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.PropertyTypeGroup;
@@ -65,6 +66,24 @@ public class Translator
         return new Role(roleCode.name(), spaceLevel);
     }
 
+    public static List<Project> translateProjects(
+            List<ch.systemsx.cisd.openbis.generic.shared.basic.dto.Project> projects)
+    {
+        ArrayList<Project> translated = new ArrayList<Project>();
+
+        for (ch.systemsx.cisd.openbis.generic.shared.basic.dto.Project project : projects)
+        {
+            translated.add(translate(project));
+        }
+
+        return translated;
+    }
+
+    static Project translate(ch.systemsx.cisd.openbis.generic.shared.basic.dto.Project project)
+    {
+        return new Project(project.getSpace().getCode(), project.getCode());
+    }
+
     public static List<Sample> translateSamples(
             Collection<ch.systemsx.cisd.openbis.generic.shared.basic.dto.Sample> privateSamples)
     {
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/IGeneralInformationService.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/IGeneralInformationService.java
index b64615df257e73b40ebf53a9be5daacc1579f1f6..16c1a62574ef461e19dc4d0df4fdb6fb8308cf15 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/IGeneralInformationService.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/IGeneralInformationService.java
@@ -220,4 +220,11 @@ public interface IGeneralInformationService extends IRpcService
     @Transactional(readOnly = true)
     @RolesAllowed(RoleWithHierarchy.INSTANCE_OBSERVER)
     public List<Experiment> listExperiments(String sessionToken, List<String> experimentIdentifiers);
+
+    /**
+     * Returns all available projects.
+     */
+    @Transactional(readOnly = true)
+    @RolesAllowed(RoleWithHierarchy.SPACE_OBSERVER)
+    public List<Project> listProjects(String sessionToken);
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/Project.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/Project.java
index 16bad61f1adbf86d10b1a82903c785d492b727f3..026d4ad2b75612137e4c48be8afdcf8c4b1f7035 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/Project.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/api/v1/dto/Project.java
@@ -19,17 +19,17 @@ package ch.systemsx.cisd.openbis.generic.shared.api.v1.dto;
 import java.io.Serializable;
 
 /**
- * Immutable value object representing a project. A project is specified by its code and the code
- * of the space to which it belongs.
- *
+ * Immutable value object representing a project. A project is specified by its code and the code of
+ * the space to which it belongs.
+ * 
  * @author Franz-Josef Elmer
  */
 public final class Project implements Serializable
 {
     private static final long serialVersionUID = 1L;
-    
+
     private final String spaceCode;
-    
+
     private final String code;
 
     /**
@@ -51,7 +51,7 @@ public final class Project implements Serializable
         }
         this.code = code;
     }
-    
+
     /**
      * Returns the space code.
      */
@@ -59,7 +59,7 @@ public final class Project implements Serializable
     {
         return spaceCode;
     }
-    
+
     /**
      * Returns the project code.
      */