diff --git a/openbis-ipad/source/core-plugins/ipad-ui/1/dss/reporting-plugins/ipad-read-service-v1/ipad_read.py b/openbis-ipad/source/core-plugins/ipad-ui/1/dss/reporting-plugins/ipad-read-service-v1/ipad_read.py
index c6852b01772931f9b47ecb8816d4af70bba071c9..8723e7c8e06ee8a166b4d0ce6822e5052f325aa2 100644
--- a/openbis-ipad/source/core-plugins/ipad-ui/1/dss/reporting-plugins/ipad-read-service-v1/ipad_read.py
+++ b/openbis-ipad/source/core-plugins/ipad-ui/1/dss/reporting-plugins/ipad-read-service-v1/ipad_read.py
@@ -1,6 +1,7 @@
+from ch.systemsx.cisd.openbis.ipad.v2.server import AbstractRequestHandler, ClientPreferencesRequestHandler, RootRequestHandler, DrillRequestHandler, NavigationRequestHandler, DetailRequestHandler, EmptyDataRequestHandler
 from ch.systemsx.cisd.openbis.dss.generic.shared.api.internal.v2 import MaterialIdentifierCollection
 from ch.systemsx.cisd.openbis.generic.shared.basic.dto import MaterialIdentifier
-from com.fasterxml.jackson.databind import ObjectMapper 
+from com.fasterxml.jackson.databind import ObjectMapper
 from ch.systemsx.cisd.openbis.generic.shared.api.v1.dto import SearchCriteria, SearchSubCriteria
 
 from datetime import datetime
@@ -21,188 +22,6 @@ def json_empty_dict():
   """Utility function to return an json-encoded empty dictionary"""
   return json_encoded_value({})
 
-class RequestHandler(object):
-	"""Abstract superclass for the handlers for concrete requests like ROOT.
-
-	This superclass defines behavior common to all requests.
-
-	Subclasses need to implement the method optional_headers(), which returns
-	a list of the optional headers they fill out.
-
-	Subclasses should implement retrieve_data to get the data they provide.
-
-	Subclasses should implement add_data_rows. In this method, they should call add_row.
-	The method add_row takes a dictionary as an argument. The keys of the dictionary match the
-	headers in the result columns. The dictionary should include data for the required columns
-	and optional ones they fill.
-
-	"""
-
-	def __init__(self, parameters, builder):
-		self.parameters = parameters
-		self.builder = builder
-		global searchService
-		self.searchService = searchService
-		self.headers = ['PERM_ID', 'REFCON'] + self.optional_headers()
-		
-	def entities_parameter(self):
-	  """A helper method to get the value of the entities parameter. Returns an empty list if no entities were specified"""
-	  entities = self.parameters.get('entities')
-	  if entities is None:
-	    return []
-	  return entities
-
-
-	def optional_headers(self):
-		"""Return a list of optional headers supported by this request. Sublass responsibility.
-
-		See add_headers() for the list of supported headers
-		"""
-		return []
-
-	def retrieve_data(self):
-		"""Get the data for the request. Subclass responsibility"""
-		pass
-
-	def add_data_rows(self):
-		"""Take the information from the data and put it into the table.
-		Subclass responsibility.
-		"""
-		pass
-
-	def add_headers(self):
-		"""Configure the headers for this request.
-
-		The possible headers come from the following list:
-			PERM_ID : A stable identifier for the object. (required)
-			REFCON : Data that is passed unchanged back to the server when a row is modified.
-				This can be used by the server to encode whatever it needs in order to
-				modify the row. (required)
-			CATEGORY : A category identifier for grouping entities.
-			SUMMARY_HEADER : A short summary of the entity.
-			SUMMARY : A potentially longer summary of the entity.
-			CHILDREN : The permIds of the children of this entity. Transmitted as JSON.
-			IDENTIFIER : An identifier for the object.
-			IMAGES : A map with keys coming from the set 'MARQUEE', 'TILED'. The values are image specs or lists of image specs.
-				Image specs are maps with the keys: 'URL' (a URL for the iamge) or 'DATA'. The data key contains a map that
-				includes the image data and may include some image metadata as well. This format has not yet been specified.
-			PROPERTIES : Properties (metadata) that should be displayed for this entity. Transmitted as JSON.
-			ROOT_LEVEL : True if the entity should be shown on the root level.
-
-		The relevant headers are determined by the request.
-		"""
-		for header in self.headers:
-			self.builder.addHeader(header)
-
-	def add_row(self, entry):
-		"""Append a row of data to the table"""
-		row = self.builder.addRow()
-		for header in self.headers:
-			value = entry.get(header)
-			if value is not None:
-				row.setCell(header, value)
-			else:
-				row.setCell(header, "")
-
-	def add_rows(self, entities):
-		"""Take a collection of dictionaries and add a row for each one"""
-		for entry in entities:
-			self.add_row(entry)
-
-	def process_request(self):
-		"""Execute the steps necessary to process the request."""
-		self.add_headers()
-		self.retrieve_data()
-		self.add_data_rows()
-
-class ClientPreferencesRequestHandler(object):
-	"""Abstract superclass for the handlers for CLIENT_PREFS request.
-
-	This request has a slightly different structure, since it does not return entities.
-
-	Subclasses should override the preferences_dict method to return the preferences dictionary. The superclass
-	implements this method with the default values for the standard keys.
-	"""
-
-	def __init__(self, parameters, builder):
-		self.parameters = parameters
-		self.builder = builder
-		self.headers = ['KEY', 'VALUE']
-
-	def preferences_dict(self):
-		"""The dictionary containing the value for the client preferences. 
-
-		Subclasses may override if they want to change any of the values. The best way to override is to call
-		default_preferences_dict then modify/extend the resulting dictionary"""
-		return self.default_preferences_dict()
-
-	def default_preferences_dict(self):
-		"""The dictionary containing the standard keys and and default values for those keys"""
-		prefs = { 
-			# The refresh interval is a value in seconds
-			'ROOT_SET_REFRESH_INTERVAL' : 60 * 30 
-		}
-		return prefs
-
-	def add_data_rows(self):
-		"""Take the information from the preferences dict and put it into the table."""
-		prefs = self.preferences_dict()
-		for key in prefs:
-			row = self.builder.addRow()
-			row.setCell('KEY', key)
-			row.setCell('VALUE', prefs[key])
-
-	def add_headers(self):
-		"""Configure the headers for this request.
-
-		For preference request, the headers are 
-			KEY : The key of the preference.
-			VALUE : The value of the preference.
-		"""
-		for header in self.headers:
-			self.builder.addHeader(header)
-
-	def process_request(self):
-		"""Execute the steps necessary to process the request."""
-		self.add_headers()
-		self.add_data_rows()
-
-class AllDataRequestHandler(RequestHandler):
-	"""Abstract Handler for the ALLDATA request."""
-
-	def optional_headers(self):
-		return ["CATEGORY", "SUMMARY_HEADER", "SUMMARY", "CHILDREN", "IDENTIFIER", "IMAGES", "PROPERTIES"]
-
-class EmptyDataRequestHandler(RequestHandler):
-	"""Return nothing to the caller."""
-
-	def add_data_rows(self):
-		pass
-
-class RootRequestHandler(RequestHandler):
-	"""Abstract Handler for the ROOT request."""
-
-	def optional_headers(self):
-		return ["CATEGORY", "SUMMARY_HEADER", "SUMMARY", "CHILDREN", "ROOT_LEVEL"]
-
-class DrillRequestHandler(RequestHandler):
-	"""Abstract Handler for the DRILL request."""
-
-	def optional_headers(self):
-		return ["CATEGORY", "SUMMARY_HEADER", "SUMMARY", "CHILDREN"]
-
-class DetailRequestHandler(RequestHandler):
-	"""Abstract Handler for the DETAIL request."""
-
-	def optional_headers(self):
-		return ["CATEGORY", "SUMMARY_HEADER", "SUMMARY", "IDENTIFIER", "IMAGES", "PROPERTIES"]
-
-class NavigationRequestHandler(RequestHandler):
-	"""Abstract Handler for the NAVIGATION request."""
-
-	def optional_headers(self):
-		return ["CATEGORY", "SUMMARY_HEADER", "SUMMARY", "ROOT_LEVEL"]
-
 #
 # END Infrastructure
 #
@@ -419,14 +238,14 @@ class ExampleRootRequestHandler(RootRequestHandler):
 	"""Handler for the ROOT request."""
 
 	def entities_parameter(self):
-		entities = super(ExampleRootRequestHandler, self).entities_parameter()
+		entities = self.getEntitiesParameter()
 		if len(entities) == 0:
 			materials_nav = navigation_dict('Targets and Compounds', [])
 			probe_nav = navigation_dict('Probes', [])
 			return [materials_nav, probe_nav]
 		return entities
 
-	def retrieve_data(self):
+	def retrieveData(self):
 		# Check which navigational entities are being requested here
 		nav_entities = self.entities_parameter()
 		nav_perm_ids = [entity['PERM_ID'] for entity in nav_entities]
@@ -438,28 +257,29 @@ class ExampleRootRequestHandler(RootRequestHandler):
 		self.material_dict_array = materials_to_dict(materials, {})
 		self.material_by_perm_id = dict([(material.getMaterialIdentifier(), material) for material in materials])
 
-	def add_data_rows(self):
+	def addDataRows(self):
 		nav_entities = self.entities_parameter()
 		nav_perm_ids = [entity['PERM_ID'] for entity in nav_entities]
 
 		if 'TARGETS AND COMPOUNDS' in nav_perm_ids:
 			children = [material_dict['PERM_ID'] for material_dict in self.material_dict_array]
 			materials_nav = navigation_dict('Targets and Compounds', children)
-			self.add_rows([materials_nav])
-			self.add_rows(self.material_dict_array)
+			self.addRows([materials_nav])
+			self.addRows(self.material_dict_array)
 
 		if 'PROBES' in nav_perm_ids:
 			children = [sample.getPermId() for sample in self.samples]
 			probe_nav = navigation_dict('Probes', children)
-			self.add_rows([probe_nav])
-			self.add_rows(samples_to_dict(self.samples, self.material_by_perm_id, {}))
+			self.addRows([probe_nav])
+			self.addRows(samples_to_dict(self.samples, self.material_by_perm_id, {}))
+
 
 class ExampleDrillRequestHandler(DrillRequestHandler):
 	"""Handler for the DRILL request."""
 
-	def retrieve_data(self):
+	def retrieveData(self):
 		# Drill only happens on samples
-		drill_samples = self.entities_parameter()
+		drill_samples = self.getEntitiesParameter()
 
 		self.samples = retrieve_samples(drill_samples)
 		material_identifiers = gather_materials(self.samples)
@@ -467,16 +287,17 @@ class ExampleDrillRequestHandler(DrillRequestHandler):
 		self.material_dict_array = materials_to_dict(materials, {})
 		self.material_by_perm_id = dict([(material.getMaterialIdentifier(), material) for material in materials])
 
-	def add_data_rows(self):
-		self.add_rows(self.material_dict_array)
-		self.add_rows(samples_to_dict(self.samples, self.material_by_perm_id, {}))
+	def addDataRows(self):
+		self.addRows(self.material_dict_array)
+		self.addRows(samples_to_dict(self.samples, self.material_by_perm_id, {}))
+
 
 class ExampleDetailRequestHandler(DetailRequestHandler):
 	"""Handler for the DETAIL request."""
 
-	def retrieve_data(self):
+	def retrieveData(self):
 		# Get the data and add a row for each data item
-		entities = self.entities_parameter()
+		entities = self.getEntitiesParameter()
 		detail_samples = [entity for entity in entities if 'SAMPLE' == entity['REFCON']['entityKind']]
 		detail_materials = [entity for entity in entities if 'MATERIAL' == entity['REFCON']['entityKind']]
 
@@ -497,21 +318,22 @@ class ExampleDetailRequestHandler(DetailRequestHandler):
 		self.material_dict_array = materials_to_dict(materials_to_return, self.material_type_properties_definitions)
 		self.material_by_perm_id = dict([(material.getMaterialIdentifier(), material) for material in materials])
 
-	def add_data_rows(self):
-		self.add_rows(self.material_dict_array)
-		self.add_rows(samples_to_dict(self.samples, self.material_by_perm_id, self.sample_type_properties_definitions))
+	def addDataRows(self):
+		self.addRows(self.material_dict_array)
+		self.addRows(samples_to_dict(self.samples, self.material_by_perm_id, self.sample_type_properties_definitions))
 
 class ExampleNavigationRequestHandler(NavigationRequestHandler):
 	"""Handler for the NAVIGATION request"""
-	def add_data_rows(self):
+	def addDataRows(self):
 		materials_nav = navigation_dict('Targets and Compounds', [])
 		probe_nav = navigation_dict('Probes', [])
-		self.add_rows([materials_nav, probe_nav])
+		self.addRows([materials_nav, probe_nav])
+
 
 class TestingNavigationRequestHandler(ExampleNavigationRequestHandler):
 	"""A version of the NAVIGATION request handler designed for testing"""
 
-	def add_data_rows(self):
+	def addDataRows(self):
 		hidden_entities = self.parameters.get("HIDE")
 		if hidden_entities is None:
 			hidden_entities = []
@@ -519,23 +341,23 @@ class TestingNavigationRequestHandler(ExampleNavigationRequestHandler):
 
 		if 'TARGETS AND COMPOUNDS' not in hidden_perm_ids:
 			materials_nav = navigation_dict('Targets and Compounds', [])
-			self.add_rows([materials_nav])
+			self.addRows([materials_nav])
 		if 'PROBES' not in hidden_perm_ids:
 			probe_nav = navigation_dict('Probes', [])
-			self.add_rows([probe_nav])
+			self.addRows([probe_nav])
 
 def aggregate(parameters, builder):
 	request_key = parameters.get('requestKey')
 	if 'CLIENT_PREFS' == request_key:
-		handler = ExampleClientPreferencesRequestHandler(parameters, builder)
+		handler = ExampleClientPreferencesRequestHandler(parameters, builder, searchService)
 	elif 'NAVIGATION' == request_key:
-		handler = TestingNavigationRequestHandler(parameters, builder)
+		handler = TestingNavigationRequestHandler(parameters, builder, searchService)
 	elif 'ROOT' == request_key:
-		handler = ExampleRootRequestHandler(parameters, builder)
+		handler = ExampleRootRequestHandler(parameters, builder, searchService)
 	elif 'DRILL' == request_key:
-		handler = ExampleDrillRequestHandler(parameters, builder)
+		handler = ExampleDrillRequestHandler(parameters, builder, searchService)
 	elif 'DETAIL' == request_key:
-		handler = ExampleDetailRequestHandler(parameters, builder)
+		handler = ExampleDetailRequestHandler(parameters, builder, searchService)
 	else:
-		handler = EmptyDataRequestHandler(parameters, builder)
-	handler.process_request()		
+		handler = EmptyDataRequestHandler(parameters, builder, searchService)
+	handler.processRequest()		
diff --git a/openbis-ipad/source/core-plugins/ipad-ui/1/dss/reporting-plugins/ipad-read-service-v1/lib/ipad-framework.jar b/openbis-ipad/source/core-plugins/ipad-ui/1/dss/reporting-plugins/ipad-read-service-v1/lib/ipad-framework.jar
new file mode 100644
index 0000000000000000000000000000000000000000..22ab38d03c7532d04a6dada46155779631697ecf
Binary files /dev/null and b/openbis-ipad/source/core-plugins/ipad-ui/1/dss/reporting-plugins/ipad-read-service-v1/lib/ipad-framework.jar differ
diff --git a/openbis-ipad/source/java/ch/systemsx/cisd/openbis/ipad/v2/server/ClientPreferencesRequestHandler.java b/openbis-ipad/source/java/ch/systemsx/cisd/openbis/ipad/v2/server/ClientPreferencesRequestHandler.java
index c42b5f4f390befaee3fc00cfaee1c3ef473d7649..fb846edc74f1a38033f82130b32d9b5787cfffa0 100644
--- a/openbis-ipad/source/java/ch/systemsx/cisd/openbis/ipad/v2/server/ClientPreferencesRequestHandler.java
+++ b/openbis-ipad/source/java/ch/systemsx/cisd/openbis/ipad/v2/server/ClientPreferencesRequestHandler.java
@@ -21,6 +21,7 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
+import ch.systemsx.cisd.openbis.dss.generic.shared.api.internal.v2.ISearchService;
 import ch.systemsx.cisd.openbis.generic.shared.managed_property.api.IRowBuilderAdaptor;
 import ch.systemsx.cisd.openbis.generic.shared.managed_property.api.ISimpleTableModelBuilderAdaptor;
 
@@ -48,11 +49,10 @@ public class ClientPreferencesRequestHandler implements IRequestHandler
      * 
      * @param parameters The request parameters.
      * @param builder A table model builder.
-     * @param searchService The service that supports searching for openBIS entities.
-     * @param optionalHeaders Non-required headers that are returned by this request.
+     * @param searchService Ignored.
      */
     protected ClientPreferencesRequestHandler(Map<String, Object> parameters,
-            ISimpleTableModelBuilderAdaptor builder)
+            ISimpleTableModelBuilderAdaptor builder, ISearchService searchService)
     {
         this.parameters = parameters;
         this.builder = builder;