diff --git a/openbis/build/build.xml b/openbis/build/build.xml
index 5cb354dc324ae055456c60f5e3f01925dccc222d..e8b23950031c03ba43247a0a8f8e7685cf52ceea 100644
--- a/openbis/build/build.xml
+++ b/openbis/build/build.xml
@@ -29,6 +29,9 @@
   <property name="admin.console" value="openbis-admin-console" />
   <property name="admin.jar.file" value="${dist}/${admin.console}.jar" />
   <property name="admin.dist.file" value="${dist}/${admin.console}.zip" />
+  <property name="query.api" value="openbis-query-api" />
+  <property name="query.api.jar.file" value="${dist}/${query.api}.jar" />
+  <property name="query.api.dist.file" value="${dist}/${query.api}.zip" />
 
   <property name="webapp.file.name" value="openBIS.war" />
   <property name="webapp.file" value="${server.dist}/${webapp.file.name}" />
@@ -157,8 +160,22 @@
     </zip>
   </target>
 
+  <target name="query-api-dist" depends="jar" 
+  	description="Makes a distribution file for Query API.">
+    <zip destfile="${query.api.dist.file}">
+      <zipfileset prefix="${query.api}" file="${query.api.jar.file}"/>
+      <zipfileset prefix="${query.api}" file="${lib}/cisd-base/cisd-base.jar"/>
+      <zipfileset prefix="${query.api}" file="${lib}/commons-codec/commons-codec.jar"/>
+      <zipfileset prefix="${query.api}" file="${lib}/commons-httpclient/commons-httpclient.jar"/>
+      <zipfileset prefix="${query.api}" file="${lib}/commons-logging/commons-logging.jar"/>
+      <zipfileset prefix="${query.api}" file="${lib}/log4j/log4j.jar"/>
+      <zipfileset prefix="${query.api}" file="${lib}/spring/spring.jar"/>
+      <zipfileset prefix="${query.api}" file="${lib}/spring/third-party/stream-supporting-httpinvoker.jar"/>
+    </zip>
+  </target>
+
   <target name="make-dist" description="Makes a distribution file." 
-  		depends="check-dictionary-syntax, clean, war, admin-console-dist">
+  		depends="check-dictionary-syntax, clean, war, admin-console-dist, query-api-dist">
     <copy file="${lib}/tomcat5/apache-tomcat.zip" todir="${server.dist}" />
     <loadfile property="tomcat.version" srcFile="${lib}/tomcat5/version.txt">
       <filterchain>
@@ -169,6 +186,7 @@
     <copy file="${original.openbis.server.dist}/server.xml" todir="${server.dist}" />
     <move file="${client.jar.file}" todir="${server.dist}" />
     <move file="${admin.dist.file}" todir="${server.dist}" />
+    <move file="${query.api.dist.file}" todir="${server.dist}" />
     <copy file="${original.server.dist}/service.properties" todir="${server.dist}" />
     <copy file="${original.server.dist}/openbis.conf" todir="${server.dist}" />
     <copy file="${original.openbis.server.dist}/openBIS.keystore" todir="${server.dist}" />
@@ -372,7 +390,7 @@
   </target>
 
   <!--
-      // Creates JAR file.
+      // Creates JAR files.
       -->
   <target name="jar" depends="compile, build-info" description="Creates project jar file.">
     <delete file="${jar.file}" />
@@ -440,7 +458,17 @@
                           commons-httpclient.jar commons-io.jar commons-logging.jar 
         	                log4j.jar jline.jar spring.jar stream-supporting-httpinvoker.jar"/>
       </manifest>
-  	</jar>
+      </jar>
+    <jar destfile="${query.api.jar.file}">
+      <zipfileset src="${jar.file}">
+        <include name="ch/systemsx/cisd/common/exceptions/**/*.class" />
+        <include name="ch/systemsx/cisd/common/spring/HttpInvokerUtils.class" />
+        <include name="ch/systemsx/cisd/openbis/**/api/**/*.class" />
+        <exclude name="ch/systemsx/cisd/openbis/**/server/api/**/*.class" />
+        <include name="**/*BuildAndEnvironmentInfo.class" />
+        <include name="*.INFO" />
+      </zipfileset>
+    </jar>
   </target>
 
   <!--
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/client/api/v1/FacadeFactory.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/client/api/v1/FacadeFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..cdae15a60f5c258c62c3b797a11c5430f433d7b3
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/client/api/v1/FacadeFactory.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2010 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.plugin.query.client.api.v1;
+
+import ch.systemsx.cisd.common.spring.HttpInvokerUtils;
+import ch.systemsx.cisd.openbis.plugin.query.server.api.v1.ResourceNames;
+import ch.systemsx.cisd.openbis.plugin.query.shared.api.v1.IQueryApiServer;
+
+/**
+ * Factory of {@link IQueryApiFacade}.
+ *
+ * @author Franz-Josef Elmer
+ */
+public class FacadeFactory
+{
+    private static final int SERVER_TIMEOUT_MIN = 5;
+
+    /**
+     * Creates a facade for specified server URL, user Id, and password.
+     */
+    public static IQueryApiFacade create(String serverURL, String userID, String password)
+    {
+        IQueryApiServer service =
+                HttpInvokerUtils.createServiceStub(IQueryApiServer.class, serverURL
+                        + ResourceNames.QUERY_PLUGIN_SERVER_URL, SERVER_TIMEOUT_MIN);
+        String sessionToken = service.tryToAuthenticateAtQueryServer(userID, password);
+        if (sessionToken == null)
+        {
+            throw new IllegalArgumentException("User " + userID + "couldn't be authenticated");
+        }
+        return new QueryApiFacade(service, sessionToken);
+    }
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/client/api/v1/IQueryApiFacade.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/client/api/v1/IQueryApiFacade.java
new file mode 100644
index 0000000000000000000000000000000000000000..0bc92a0759954875b537fa1014a8afc3fc6cb679
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/client/api/v1/IQueryApiFacade.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2010 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.plugin.query.client.api.v1;
+
+import java.util.List;
+
+import ch.systemsx.cisd.openbis.plugin.query.shared.api.v1.dto.QueryDescription;
+import ch.systemsx.cisd.openbis.plugin.query.shared.api.v1.dto.QueryTableModel;
+
+/**
+ * Facade for openBIS query service.
+ *
+ * @author Franz-Josef Elmer
+ */
+public interface IQueryApiFacade
+{
+    /**
+     * Lists all queries the user has access rights.
+     */
+    public List<QueryDescription> listQueries();
+
+    /**
+     * Executes specified query by using specified parameter values.
+     */
+    public QueryTableModel executeQuery(QueryDescription queryDescription,
+            List<String> parameterValues);
+
+    /**
+     * Logs current user out.
+     */
+    public void logout();
+    
+}
\ No newline at end of file
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/client/api/v1/QueryApiFacade.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/client/api/v1/QueryApiFacade.java
index c246d32d28b36ebdbe7f5441e8aae599ebe13dee..2845114c62cb2770f26c8f993b9edcca046e12e1 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/client/api/v1/QueryApiFacade.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/client/api/v1/QueryApiFacade.java
@@ -16,33 +16,20 @@
 
 package ch.systemsx.cisd.openbis.plugin.query.client.api.v1;
 
+import java.util.HashMap;
 import java.util.List;
 
-import ch.systemsx.cisd.common.spring.HttpInvokerUtils;
-import ch.systemsx.cisd.openbis.plugin.query.server.api.v1.ResourceNames;
 import ch.systemsx.cisd.openbis.plugin.query.shared.api.v1.IQueryApiServer;
 import ch.systemsx.cisd.openbis.plugin.query.shared.api.v1.dto.QueryDescription;
+import ch.systemsx.cisd.openbis.plugin.query.shared.api.v1.dto.QueryTableModel;
 
 /**
  * 
  *
  * @author Franz-Josef Elmer
  */
-public class QueryApiFacade
+class QueryApiFacade implements IQueryApiFacade
 {
-    private static final int SERVER_TIMEOUT_MIN = 5;
-    
-    public static QueryApiFacade create(String serverURL, String userID, String password)
-    {
-        IQueryApiServer service = HttpInvokerUtils.createServiceStub(IQueryApiServer.class, serverURL + ResourceNames.QUERY_PLUGIN_SERVER_URL, SERVER_TIMEOUT_MIN);
-        String sessionToken = service.tryToAuthenticateAtQueryServer(userID, password);
-        if (sessionToken == null)
-        {
-            throw new IllegalArgumentException("User " + userID + "couldn't be authenticated");
-        }
-        return new QueryApiFacade(service, sessionToken);
-    }
-    
     private final IQueryApiServer service;
     private final String sessionToken;
     
@@ -61,4 +48,16 @@ public class QueryApiFacade
     {
         return service.listQueries(sessionToken);
     }
+    
+    public QueryTableModel executeQuery(QueryDescription queryDescription, List<String> parameterValues)
+    {
+        long id = queryDescription.getId();
+        HashMap<String, String> parameterBindings = new HashMap<String, String>();
+        List<String> parameters = queryDescription.getParameters();
+        for (int i = 0, n = parameters.size(); i < n; i++)
+        {
+            parameterBindings.put(parameters.get(i), parameterValues.get(i));
+        }
+        return service.executeQuery(sessionToken, id, parameterBindings);
+    }
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/client/api/v1/QueryApiTest.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/client/api/v1/QueryApiTest.java
index d111a5d5a84ff336592045810a28f692a76b8ef4..ee53b53a6af57756131391bd03280833a0d5eef1 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/client/api/v1/QueryApiTest.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/client/api/v1/QueryApiTest.java
@@ -39,7 +39,7 @@ public class QueryApiTest
         String userID = args[1];
         String password = args[2];
         
-        QueryApiFacade facade = QueryApiFacade.create(serverURL, userID, password);
+        IQueryApiFacade facade = FacadeFactory.create(serverURL, userID, password);
         
         List<QueryDescription> queries = facade.listQueries();
         for (QueryDescription queryDescription : queries)
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/server/api/v1/QueryApiLogger.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/server/api/v1/QueryApiLogger.java
index cb34fabbcaf11949fbce02dc33be1c5befa44f8c..85a9a8687ad32bbbd768c7e1ff10254d2a84e08f 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/server/api/v1/QueryApiLogger.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/server/api/v1/QueryApiLogger.java
@@ -40,20 +40,23 @@ class QueryApiLogger extends AbstractServerLogger implements IQueryApiServer
         super(sessionManager, context);
     }
 
-    public QueryTableModel executeQuery(long queryID, Map<String, String> parameterBindings)
+    public String tryToAuthenticateAtQueryServer(String userID, String userPassword)
     {
         return null;
     }
-
+    
     public List<QueryDescription> listQueries(String sessionToken)
     {
         logAccess(sessionToken, "list_queries");
         return null;
     }
 
-    public String tryToAuthenticateAtQueryServer(String userID, String userPassword)
+    public QueryTableModel executeQuery(String sessionToken, long queryID,
+            Map<String, String> parameterBindings)
     {
+        logAccess(sessionToken, "execute_query", "QUERY(%s) PARAMETERS(%s)", queryID,
+                parameterBindings.size());
         return null;
     }
-
+    
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/server/api/v1/QueryApiServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/server/api/v1/QueryApiServer.java
index 49da8e315193865b77b79f105de3810abf4b22c4..1074a89e069890452b2dc6670e85ad8a076b25b7 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/server/api/v1/QueryApiServer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/server/api/v1/QueryApiServer.java
@@ -16,9 +16,11 @@
 
 package ch.systemsx.cisd.openbis.plugin.query.server.api.v1;
 
+import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 
 import javax.annotation.Resource;
 
@@ -26,12 +28,23 @@ import org.springframework.stereotype.Component;
 
 import ch.systemsx.cisd.common.spring.IInvocationLoggerContext;
 import ch.systemsx.cisd.openbis.generic.server.AbstractServer;
+import ch.systemsx.cisd.openbis.generic.shared.basic.TechId;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DoubleTableCell;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.ISerializableComparable;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.IntegerTableCell;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.StringTableCell;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModel;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelColumnHeader;
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.TableModelRow;
 import ch.systemsx.cisd.openbis.generic.shared.dto.SessionContextDTO;
 import ch.systemsx.cisd.openbis.plugin.query.shared.IQueryServer;
 import ch.systemsx.cisd.openbis.plugin.query.shared.api.v1.IQueryApiServer;
 import ch.systemsx.cisd.openbis.plugin.query.shared.api.v1.dto.QueryDescription;
+import ch.systemsx.cisd.openbis.plugin.query.shared.api.v1.dto.QueryTableColumn;
+import ch.systemsx.cisd.openbis.plugin.query.shared.api.v1.dto.QueryTableColumnDataType;
 import ch.systemsx.cisd.openbis.plugin.query.shared.api.v1.dto.QueryTableModel;
 import ch.systemsx.cisd.openbis.plugin.query.shared.basic.dto.QueryExpression;
+import ch.systemsx.cisd.openbis.plugin.query.shared.basic.dto.QueryParameterBindings;
 
 /**
  * 
@@ -71,10 +84,48 @@ public class QueryApiServer extends AbstractServer<IQueryApiServer> implements I
         return result;
     }
 
-    public QueryTableModel executeQuery(long queryID, Map<String, String> parameterBindings)
+    public QueryTableModel executeQuery(String sessionToken, long queryID,
+            Map<String, String> parameterBindings)
     {
-        // TODO Auto-generated method stub
-        return null;
+        QueryParameterBindings bindings = new QueryParameterBindings();
+        for (Entry<String, String> entry : parameterBindings.entrySet())
+        {
+            bindings.addBinding(entry.getKey(), entry.getValue());
+        }
+        TableModel result = queryServer.queryDatabase(sessionToken, new TechId(queryID), bindings);
+        List<TableModelColumnHeader> headers = result.getHeader();
+        ArrayList<QueryTableColumn> translatedHeaders = new ArrayList<QueryTableColumn>();
+        for (TableModelColumnHeader header : headers)
+        {
+            String title = header.getTitle();
+            QueryTableColumnDataType dataType = Util.translate(header.getDataType());
+            translatedHeaders.add(new QueryTableColumn(title, dataType));
+        }
+        QueryTableModel tableModel = new QueryTableModel(translatedHeaders);
+        List<TableModelRow> rows = result.getRows();
+        for (TableModelRow row : rows)
+        {
+            List<ISerializableComparable> values = row.getValues();
+            Serializable[] translatedValues = new Serializable[values.size()];
+            for (int i = 0, n = values.size(); i < n; i++)
+            {
+                ISerializableComparable value = values.get(i);
+                Serializable translatedValue = null;
+                if (value instanceof IntegerTableCell)
+                {
+                    translatedValue = ((IntegerTableCell) value).getNumber();
+                } else if (value instanceof DoubleTableCell)
+                {
+                    translatedValue = ((DoubleTableCell) value).getNumber();
+                } else if (value instanceof StringTableCell)
+                {
+                    translatedValue = ((StringTableCell) value).toString();
+                }
+                translatedValues[i] = translatedValue;
+            }
+            tableModel.addRow(translatedValues);
+        }
+        return tableModel;
     }
     
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/server/api/v1/QueryServiceServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/server/api/v1/QueryServiceServer.java
index 97e16d43a288b8d45336c3e547f537cbb8f0aefb..6743ccac5d761280acd841366d6e0bb3ba3d4785 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/server/api/v1/QueryServiceServer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/server/api/v1/QueryServiceServer.java
@@ -22,6 +22,7 @@ import org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.RequestMapping;
 
+import ch.systemsx.cisd.common.spring.ServiceExceptionTranslator;
 import ch.systemsx.cisd.openbis.plugin.query.shared.api.v1.IQueryApiServer;
 
 /**
@@ -42,6 +43,7 @@ public class QueryServiceServer extends HttpInvokerServiceExporter
     {
         setServiceInterface(IQueryApiServer.class);
         setService(server);
+        setInterceptors(new Object[] {new ServiceExceptionTranslator()});
         super.afterPropertiesSet();
     }
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/server/api/v1/Util.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/server/api/v1/Util.java
new file mode 100644
index 0000000000000000000000000000000000000000..27867f21e6436df8a21dc46291da6da10e442639
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/server/api/v1/Util.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2010 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.plugin.query.server.api.v1;
+
+import ch.systemsx.cisd.openbis.generic.shared.basic.dto.DataTypeCode;
+import ch.systemsx.cisd.openbis.plugin.query.shared.api.v1.dto.QueryTableColumnDataType;
+
+/**
+ * 
+ *
+ * @author Franz-Josef Elmer
+ */
+class Util
+{
+    static QueryTableColumnDataType translate(DataTypeCode dataTypeCode)
+    {
+        switch (dataTypeCode)
+        {
+            case INTEGER: return QueryTableColumnDataType.LONG;
+            case REAL: return QueryTableColumnDataType.DOUBLE;
+            default: return QueryTableColumnDataType.STRING;
+        }
+    }
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/shared/api/v1/IQueryApiServer.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/shared/api/v1/IQueryApiServer.java
index 729268f3ecd11e8e6f193c82007b8d3f62b9dbc3..95ffe2bc9746ab2da8b1793bf761b5a1657d1dab 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/shared/api/v1/IQueryApiServer.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/shared/api/v1/IQueryApiServer.java
@@ -55,6 +55,6 @@ public interface IQueryApiServer
      * Executes specified query using specified parameter bindings.
      */
     @Transactional(readOnly = true)
-    public QueryTableModel executeQuery(long queryID, Map<String, String> parameterBindings);
+    public QueryTableModel executeQuery(String sessionToken, long queryID, Map<String, String> parameterBindings);
 
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/shared/api/v1/dto/QueryDescription.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/shared/api/v1/dto/QueryDescription.java
index 16761123916f015ddde347053e64c651697c2175..7509ea742064b6e6ed95820236a3edc86f0902f4 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/shared/api/v1/dto/QueryDescription.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/shared/api/v1/dto/QueryDescription.java
@@ -90,4 +90,41 @@ public class QueryDescription implements Serializable
     {
         this.parameters = parameters;
     }
+
+    /**
+     * Returns <code>true</code> if and only if the specified object is of type
+     * {@link QueryDescription} and has the same ID as this.
+     */
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (this == obj)
+        {
+            return true;
+        }
+        if (obj instanceof QueryDescription == false)
+        {
+            return false;
+        }
+        QueryDescription queryDescription = (QueryDescription) obj;
+        return queryDescription.id == id;
+    }
+
+    /**
+     * Returns the ID.
+     */
+    @Override
+    public int hashCode()
+    {
+        return (int) id;
+    }
+
+    /**
+     * Returns the name.
+     */
+    @Override
+    public String toString()
+    {
+        return name;
+    }
 }
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/shared/api/v1/dto/QueryTableColumn.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/shared/api/v1/dto/QueryTableColumn.java
new file mode 100644
index 0000000000000000000000000000000000000000..9522a5042a025f22fadc8ecb81abf5e5b3133f91
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/shared/api/v1/dto/QueryTableColumn.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2010 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.plugin.query.shared.api.v1.dto;
+
+import java.io.Serializable;
+
+/**
+ * Column of query data. Defines title and data type.
+ *
+ * @author Franz-Josef Elmer
+ */
+public class QueryTableColumn implements Serializable
+{
+    private static final long serialVersionUID = 1L;
+
+    private final String title;
+    private final QueryTableColumnDataType dataType;
+    
+    /**
+     * Creates an instance for specified title and data type.
+     */
+    public QueryTableColumn(String title, QueryTableColumnDataType dataType)
+    {
+        this.title = title;
+        this.dataType = dataType;
+    }
+
+    /**
+     * Returns the title.
+     */
+    public String getTitle()
+    {
+        return title;
+    }
+
+    /**
+     * Returns the data type.
+     */
+    public QueryTableColumnDataType getDataType()
+    {
+        return dataType;
+    }
+    
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/shared/api/v1/dto/QueryTableColumnDataType.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/shared/api/v1/dto/QueryTableColumnDataType.java
new file mode 100644
index 0000000000000000000000000000000000000000..cca773acc049aa461c00b09651bcb4f988cf390c
--- /dev/null
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/shared/api/v1/dto/QueryTableColumnDataType.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2010 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.plugin.query.shared.api.v1.dto;
+
+/**
+ * Data types of {@link QueryTableModel}.
+ *
+ * @author Franz-Josef Elmer
+ */
+public enum QueryTableColumnDataType
+{
+    LONG, DOUBLE, STRING;
+}
diff --git a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/shared/api/v1/dto/QueryTableModel.java b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/shared/api/v1/dto/QueryTableModel.java
index 794617541d855e4f3d5eb229b26fa0ccfacaf539..5735c90ed4cb9ba48eea6d052d868ec215916edf 100644
--- a/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/shared/api/v1/dto/QueryTableModel.java
+++ b/openbis/source/java/ch/systemsx/cisd/openbis/plugin/query/shared/api/v1/dto/QueryTableModel.java
@@ -17,14 +17,62 @@
 package ch.systemsx.cisd.openbis.plugin.query.shared.api.v1.dto;
 
 import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
- * 
+ * Model of query data.
  *
  * @author Franz-Josef Elmer
  */
 public class QueryTableModel implements Serializable
 {
     private static final long serialVersionUID = 1L;
+    private final List<QueryTableColumn> columns;
+    private final List<Serializable[]> rows;
+    
+    /**
+     * Creates an instance for the specified columns.
+     */
+    public QueryTableModel(List<QueryTableColumn> columns)
+    {
+        this.columns = columns;
+        rows = new ArrayList<Serializable[]>();
+    }
 
+    /**
+     * Returns columns as specified in the constructor.
+     */
+    public List<QueryTableColumn> getColumns()
+    {
+        return columns;
+    }
+
+    /**
+     * Adds a row of values.
+     * 
+     * @throws IllegalArgumentException if the number of values is not the same as the number of
+     *             columns.
+     */
+    public void addRow(Serializable[] values)
+    {
+        if (values == null)
+        {
+            throw new IllegalArgumentException("Unspecified row.");
+        }
+        if (values.length != columns.size())
+        {
+            throw new IllegalArgumentException("Row has " + values.length + " instead of " + columns.size() + ".");
+        }
+        rows.add(values);
+    }
+
+    /**
+     * Gets all rows.
+     */
+    public List<Serializable[]> getRows()
+    {
+        return rows;
+    }
+    
 }