diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/demo/DemoReportingPlugin.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/demo/DemoReportingPlugin.java
index b0bab2812e2e27ef666d58b33f572ed4b4c4b728..5600c0598792b98880c4eb822e462157ab8808a4 100644
--- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/demo/DemoReportingPlugin.java
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/demo/DemoReportingPlugin.java
@@ -17,15 +17,17 @@
 package ch.systemsx.cisd.openbis.dss.generic.server.plugins.demo;
 
 import java.io.File;
-import java.io.IOException;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Properties;
 
-import org.apache.commons.io.FileSystemUtils;
+import org.apache.commons.io.FileUtils;
 
 import ch.systemsx.cisd.common.filesystem.FileUtilities;
 import ch.systemsx.cisd.openbis.dss.generic.server.plugins.tasks.IReportingPluginTask;
+import ch.systemsx.cisd.openbis.dss.generic.server.plugins.tasks.TableModelBuilder;
 import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModel;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModel.TableModelColumnType;
 import ch.systemsx.cisd.openbis.generic.shared.dto.DatasetDescription;
 
 /**
@@ -43,73 +45,62 @@ public class DemoReportingPlugin extends AbstractDatastorePlugin implements IRep
     public TableModel createReport(List<DatasetDescription> datasets)
     {
         System.out.println("Reporting from the following datasets has been requested: " + datasets);
-        StringBuffer sb = new StringBuffer();
-        sb
-                .append("<table border=1><tr><td>dataset_code</td><td>name</td><td>kind</td><td>size</td></tr>");
+        TableModelBuilder builder = new TableModelBuilder();
+        builder.addHeader("Dataset code", TableModelColumnType.TEXT);
+        builder.addHeader("Name", TableModelColumnType.TEXT);
+        builder.addHeader("Size", TableModelColumnType.INTEGER);
         for (DatasetDescription dataset : datasets)
         {
             File file = getOriginalDir(dataset);
             if (file.isDirectory())
             {
-                describe(sb, dataset, file);
+                describe(builder, dataset, file);
             } else
             {
-                describeUnknown(sb, dataset, file);
+                describeUnknown(builder, dataset, file);
             }
         }
-        sb.append("</table>");
-        return new TableModel(sb.toString());
+        return builder.getTableModel();
     }
 
-    private void describeUnknown(StringBuffer sb, DatasetDescription dataset, File file)
+    private static void describe(TableModelBuilder builder, DatasetDescription dataset, File file)
     {
-        sb.append("<tr>");
-        appendCell(sb, dataset.getDatasetCode());
-        appendCell(sb, file.getName());
-        appendCell(sb, "unknown");
-        appendCell(sb, "[does not exist]");
-        sb.append("</tr>");
-    }
-
-    private static void describe(StringBuffer sb, DatasetDescription dataset, File file)
-    {
-        sb.append("<tr>");
-        appendCell(sb, dataset.getDatasetCode());
-        appendCell(sb, file.getName());
         if (file.isFile())
         {
-            appendCell(sb, "file");
-            describeFile(sb, file);
+            describeFile(builder, dataset, file);
         } else
         {
-            appendCell(sb, "dir");
             File[] datasetFiles = FileUtilities.listFiles(file);
             for (File datasetFile : datasetFiles)
             {
-                describe(sb, dataset, datasetFile);
+                describe(builder, dataset, datasetFile);
             }
         }
-        sb.append("</tr>");
     }
 
-    private static void describeFile(StringBuffer sb, File file)
+    private void describeUnknown(TableModelBuilder builder, DatasetDescription dataset, File file)
     {
-        String size;
-        try
-        {
-            size = "" + FileSystemUtils.freeSpaceKb(file.getCanonicalPath());
-        } catch (IOException ex)
-        {
-            size = "unknown";
-        }
-        appendCell(sb, size);
+        String datasetCode = dataset.getDatasetCode();
+        List<String> row = Arrays.asList(datasetCode, file.getName(), "[does not exist]");
+        builder.addRow(row);
     }
 
-    private static void appendCell(StringBuffer sb, String text)
+    private static void describeFile(TableModelBuilder builder, DatasetDescription dataset,
+            File file)
     {
-        sb.append("<td>");
-        sb.append(text);
-        sb.append("</td>");
+        List<String> row =
+                Arrays.asList(dataset.getDatasetCode(), file.getName(), "" + getSize(file));
+        builder.addRow(row);
     }
 
+    private static long getSize(File file)
+    {
+        if (file.isFile())
+        {
+            return file.length();
+        } else
+        {
+            return FileUtils.sizeOfDirectory(file);
+        }
+    }
 }
diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/tasks/TableModelBuilder.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/tasks/TableModelBuilder.java
new file mode 100644
index 0000000000000000000000000000000000000000..653d8c4dc4918f863dd390dc05cf49f264edca8e
--- /dev/null
+++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/generic/server/plugins/tasks/TableModelBuilder.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2009 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.generic.server.plugins.tasks;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModel;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelRow;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModel.TableModelColumnHeader;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModel.TableModelColumnType;
+
+/**
+ * Helps in building a {@link TableModel}
+ * 
+ * @author Tomasz Pylak
+ */
+public class TableModelBuilder
+{
+    private List<TableModelRow> rows;
+
+    private List<TableModelColumnHeader> header;
+
+    public TableModelBuilder()
+    {
+        this.rows = new ArrayList<TableModelRow>();
+        this.header = new ArrayList<TableModelColumnHeader>();
+    }
+
+    public void addHeader(String title, TableModelColumnType type)
+    {
+        header.add(new TableModelColumnHeader(title, type));
+    }
+
+    public void addRow(List<String> values)
+    {
+        assert values.size() == header.size() : "header has different number of columns than a row";
+        rows.add(new TableModelRow(values));
+    }
+
+    public TableModel getTableModel()
+    {
+        return new TableModel(header, rows);
+    }
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/AbstractSimpleBrowserGrid.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/AbstractSimpleBrowserGrid.java
index 2ab0cc242ef467cfc7108f097c60c693f2b40624..b3131ed38aa8f6c078b50809d1c2957fcd4c58bf 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/AbstractSimpleBrowserGrid.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/client/web/client/application/ui/grid/AbstractSimpleBrowserGrid.java
@@ -55,7 +55,7 @@ abstract public class AbstractSimpleBrowserGrid<T/* Entity */> extends
     }
 
     @Override
-    protected final boolean isRefreshEnabled()
+    protected boolean isRefreshEnabled()
     {
         return true;
     }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/TableModel.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/TableModel.java
index df9acdbe7370c677eb48ea31b08615e117d2b9cc..bc22617f6ed7f04b15e182a41c6d9e3df419d9d1 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/TableModel.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/TableModel.java
@@ -17,6 +17,7 @@
 package ch.systemsx.cisd.openbis.generic.shared.basic.dto;
 
 import java.io.Serializable;
+import java.util.List;
 
 import com.google.gwt.user.client.rpc.IsSerializable;
 
@@ -25,21 +26,38 @@ import com.google.gwt.user.client.rpc.IsSerializable;
  * 
  * @author Tomasz Pylak
  */
-// TODO 2009-07-02, Tomasz Pylak: implement me. This stub holds just one string.
 public class TableModel implements IsSerializable, Serializable
 {
     private static final long serialVersionUID = ServiceVersionHolder.VERSION;
 
-    private String content;
+    private List<TableModelRow> rows;
 
-    public TableModel(String content)
+    private List<TableModelColumnHeader> header;
+
+    public TableModel(List<TableModelColumnHeader> header, List<TableModelRow> rows)
+    {
+        this.rows = rows;
+        this.header = header;
+        validate();
+    }
+
+    private void validate()
+    {
+        int columnsNo = header.size();
+        for (TableModelRow row : rows)
+        {
+            assert row.getValues().size() == columnsNo : "row has a different number of columns than the table header";
+        }
+    }
+
+    public List<TableModelRow> getRows()
     {
-        this.content = content;
+        return rows;
     }
 
-    public String getContent()
+    public List<TableModelColumnHeader> getHeader()
     {
-        return content;
+        return header;
     }
 
     // GWT only
@@ -50,9 +68,65 @@ public class TableModel implements IsSerializable, Serializable
 
     // GWT only
     @SuppressWarnings("unused")
-    private void setContent(String content)
+    private void setRows(List<TableModelRow> rows)
     {
-        this.content = content;
+        this.rows = rows;
     }
 
+    // GWT only
+    @SuppressWarnings("unused")
+    private void setHeader(List<TableModelColumnHeader> header)
+    {
+        this.header = header;
+    }
+
+    public static enum TableModelColumnType implements IsSerializable
+    {
+        DATE, INTEGER, REAL, TEXT, BOOLEAN
+    }
+
+    public static class TableModelColumnHeader implements IsSerializable, Serializable
+    {
+        private static final long serialVersionUID = ServiceVersionHolder.VERSION;
+
+        private String title;
+
+        private TableModelColumnType type;
+
+        public TableModelColumnHeader(String title, TableModelColumnType type)
+        {
+            this.title = title;
+            this.type = type;
+        }
+
+        public String getTitle()
+        {
+            return title;
+        }
+
+        public TableModelColumnType getType()
+        {
+            return type;
+        }
+
+        // GWT only
+        @SuppressWarnings("unused")
+        private TableModelColumnHeader()
+        {
+        }
+
+        // GWT only
+        @SuppressWarnings("unused")
+        private void setTitle(String title)
+        {
+            this.title = title;
+        }
+
+        // GWT only
+        @SuppressWarnings("unused")
+        private void setType(TableModelColumnType type)
+        {
+            this.type = type;
+        }
+    }
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/TableModelRow.java b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/TableModelRow.java
new file mode 100644
index 0000000000000000000000000000000000000000..b42c0f2bf3d32c4bff5abea946cd16ca36cc320c
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/generic/shared/basic/dto/TableModelRow.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2009 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.generic.shared.basic.dto;
+
+import java.io.Serializable;
+import java.util.List;
+
+import com.google.gwt.user.client.rpc.IsSerializable;
+
+/**
+ * The content of one row, without the header specification.
+ * 
+ * @author Tomasz Pylak
+ */
+public class TableModelRow implements IsSerializable, Serializable
+{
+    private static final long serialVersionUID = ServiceVersionHolder.VERSION;
+
+    // values in each column from left to right
+    private List<String> values;
+
+    public TableModelRow(List<String> values)
+    {
+        this.values = values;
+    }
+
+    public List<String> getValues()
+    {
+        return values;
+    }
+
+    // ---------------------------
+
+    // GWT only
+    @SuppressWarnings("unused")
+    private TableModelRow()
+    {
+    }
+
+    // GWT only
+    @SuppressWarnings("unused")
+    private void setValues(List<String> values)
+    {
+        this.values = values;
+    }
+
+}