diff --git a/data-discovery/Makefile b/data-discovery/Makefile
index 5349b91183a1ce2b77e1d25938789317635d5946..b48fd27c96761a38709f42a3d9abe1e35393dd11 100644
--- a/data-discovery/Makefile
+++ b/data-discovery/Makefile
@@ -12,7 +12,7 @@ AUTH_DIR    := ../auth
 # all sources
 #IA2CONVFILTER = $(AUTH_DIR)/src/main/java/IA2TokenConvFilter.java
 AUTHFILTERS  = $(wildcard $(AUTH_DIR)/src/main/java/*Filter.java) $(AUTH_DIR)/src/main/java/AuthPolicy.java
-SRC_DIR  = src/main/java/common:src/main/java/output:src/main/java/search:src/main/java/webapi:$(AUTH_DIR)/src/main/java
+SRC_DIR  = src/main/java/common:src/main/java/search:src/main/java/webapi:src/main/java/webapi/formatfilter:src/main/java/webapi/authzfilter:$(AUTH_DIR)/src/main/java
 VOSI     = src/main/java/vosi/VlkbServletFile.java
 FILTERS  = $(wildcard src/main/java/webapi/*Filter.java)
 SERVLETS = $(wildcard src/main/java/webapi/*Servlet.java)
@@ -23,12 +23,12 @@ CLASSPATH = $(LIB_DIR)/*
 
 .PHONY: build
 build:
-	echo "class Version { static String asString = \"$(VERSION)\";}" > src/main/java/output/Version.java
+	echo "class Version { static String asString = \"$(VERSION)\";}" > src/main/java/webapi/Version.java
 	javac $(JFLAGS) -cp :$(CLASSPATH) -sourcepath $(SRC_DIR) -d $(CLASS_DIR) $(SERVLETS) $(FILTERS) $(AUTHFILTERS) $(VOSI)
 
 .PHONY: clean
 clean : 
-	rm -fr src/main/java/output/Version.java target
+	rm -fr src/main/java/webapi/Version.java target
 
 
 .PHONY: install
diff --git a/data-discovery/src/main/java/webapi/DBConn.java b/data-discovery/src/main/java/common/DBConn.java
similarity index 100%
rename from data-discovery/src/main/java/webapi/DBConn.java
rename to data-discovery/src/main/java/common/DBConn.java
diff --git a/data-discovery/src/main/java/common/Subsurvey.java b/data-discovery/src/main/java/common/Subsurvey.java
deleted file mode 100644
index b8550a1c64c8b08a473dfecf82f599bbe26fdd9a..0000000000000000000000000000000000000000
--- a/data-discovery/src/main/java/common/Subsurvey.java
+++ /dev/null
@@ -1,117 +0,0 @@
-
-import java.util.logging.Logger;
-
-/* for loadSubsurveys from csv */
-import com.opencsv.*;
-import com.opencsv.exceptions.*;
-import java.io.FileReader;
-import java.io.FileNotFoundException;
-import java.util.Map;
-import java.util.List;
-import java.util.ArrayList;
-import java.io.IOException;
-
-/* NOTE originally was in search/output : designed for serializing search output xml */
-
-class Subsurvey
-{
-   private static final Logger LOGGER = Logger.getLogger("Subsurvey");
-
-   String description;
-   String surveyname;
-   String species;
-   String transition;
-   double rf; // rest frequency
-   String rf_unit;
-   String vel_unit;
-   Dataset[] datasetArr;
-
-
-   Subsurvey() { datasetArr = null; }
-   Subsurvey(Subsurvey ss)
-   {
-    this.description = ss.description;;
-    this.surveyname = ss.surveyname;
-    this.species = ss.species;
-    this.transition = ss.transition;
-    this.rf = ss.rf; 
-    this.rf_unit = ss.rf_unit;
-    this.vel_unit = ss.vel_unit;
-      this.datasetArr = null;
-   }
-
-   String id() { return (this.surveyname + " " + this.species + " "  + this.transition); }
-
-   boolean matches(String id) { return id.equals(this.id()); }
-
-
-   static public Subsurvey findSubsurvey(Subsurvey[] dbSubsurveys, String subsurvey_id)
-   {
-      for(Subsurvey curr : dbSubsurveys)
-      {
-         if(curr.matches(subsurvey_id))
-         {
-            return curr;
-         }
-      }
-
-      throw new AssertionError(subsurvey_id + " not found in surveys table");
-   }
-
-   public static Subsurvey[] loadSubsurveys(String csvFilename)
-   {
-      List<Subsurvey> subsurveyList = new ArrayList<>();
-
-      try
-      {
-         // FIXME parser not robust:
-         // * eats space-character also within the field:  ,  hu ha ,  --> "huha" not "hu ha"
-         // * double quote used inside string not escaped: ,"blabla 25\" res",
-         // * last record (line) is missing if that line is not closed with EOL
-         // * UTF-8 or US-ASACII ?
-         // * else ? how to validate/verify correctness
-
-         CSVReaderHeaderAware csvReader = new CSVReaderHeaderAware(new FileReader(csvFilename));
-
-         Map<String, String> values;
-
-         while ((values = csvReader.readMap()) != null)
-         {
-            Subsurvey subsurvey = new Subsurvey();
-
-            subsurvey.description   = values.get("description");
-            subsurvey.surveyname    = values.get("name");
-            subsurvey.species       = values.get("species");
-            subsurvey.transition    = values.get("transition");
-            subsurvey.rf            = Double.parseDouble(values.get("rest_frequency"));
-            subsurvey.rf_unit       = values.get("restf_fits_unit");
-            subsurvey.vel_unit      = values.get("velocity_fits_unit");
-
-            subsurveyList.add(subsurvey);
-         }
-
-
-      }
-      catch(IOException ex) 
-      {
-         LOGGER.info("Error while loading [" + csvFilename + "]: " + ex.getMessage());
-         //return null;
-         //throw new IllegalStateException("Error while loading " + csvFilename + " file", ex);
-      }
-      catch(CsvValidationException ex) 
-      {
-         LOGGER.info("Error while reading [" + csvFilename + "]: " + ex.getMessage());
-         //return null;
-         //throw new IllegalStateException("Error while reading " + csvFilename + " file", ex);
-      }
-
-      return subsurveyList.toArray(new Subsurvey[0]);
-   }
-
-
-
-
-
-}
-
-
diff --git a/data-discovery/src/main/java/output/SearchOutputData.java b/data-discovery/src/main/java/output/SearchOutputData.java
deleted file mode 100644
index 9d2ab54505564070c147403d344ac8dedaa7c193..0000000000000000000000000000000000000000
--- a/data-discovery/src/main/java/output/SearchOutputData.java
+++ /dev/null
@@ -1,83 +0,0 @@
-import java.util.logging.Logger;
-import java.util.List;
-import java.util.ArrayList;
-
-class SearchOutputData
-{
-   private static final Logger LOGGER = Logger.getLogger("SearchOutputData");
-
-   String description;
-   int datacubeCount;
-   String versionString;
-   Subsurvey[] subsurveyArr;
-
-
-   public static SearchOutputData  marshall(Dataset[] datasetArr,
-         /*SubsurveyId subsurveyId,*/ Subsurvey[] dbSubsurveys,
-         String mergeUrlRoot, String mergeQueryString)
-   {
-      SearchOutputData sod = new SearchOutputData();
-
-      sod.description = "Via Lactea Knowledge Base response (Search by pgSphere)";
-      sod.versionString = "Search (pgSphere) version " + Version.asString;
-      sod.datacubeCount = datasetArr.length;
-
-      sod.subsurveyArr = groupBySubsurveys(datasetArr, /*subsurveyId,*/ dbSubsurveys, mergeUrlRoot, mergeQueryString);
-      return sod;
-   }
-
-   // assumes datasetArr is already ordered by subsurveys
-   private static Subsurvey[] groupBySubsurveys(Dataset[] datasetArr,
-         /*SubsurveyId subsurveyId,*/ Subsurvey[] dbSubsurveys,
-         String mergeUrlRoot, String mergeQueryString)
-   {
-      List<Subsurvey> subsurveyList = new ArrayList<Subsurvey>();
-
-      if(datasetArr.length > 0)
-      {
-         List<Dataset> datasetList  = new ArrayList<Dataset>();
-         String prevSubsurveyId = datasetArr[0].subsurvey_id;
-
-         for(Dataset dataset : datasetArr)
-         {
-
-            if( ! prevSubsurveyId.equals(dataset.subsurvey_id) )
-            {
-               if( false )//Dataset.areDatasetsMergeable(datasetList) )
-               {
-                  Dataset mergedDataset = new Dataset(datasetList, /*subsurveyId,*/ mergeUrlRoot, mergeQueryString);
-                  datasetList.add(mergedDataset);
-               }
-
-               Subsurvey subsurvey = new Subsurvey(Subsurvey.findSubsurvey(dbSubsurveys, prevSubsurveyId));
-
-               subsurvey.datasetArr = datasetList.toArray(new Dataset[0]);
-               subsurveyList.add(subsurvey);
-
-               datasetList.clear();
-            }
-
-            datasetList.add( dataset );
-
-            prevSubsurveyId = dataset.subsurvey_id;
-         }
-
-         if( false )//Dataset.areDatasetsMergeable(datasetList) )
-         {
-            Dataset mergedDataset = new Dataset(datasetList, /*subsurveyId,*/ mergeUrlRoot, mergeQueryString);
-            datasetList.add(mergedDataset);
-         }
-
-         Subsurvey subsurvey = new Subsurvey(Subsurvey.findSubsurvey(dbSubsurveys, prevSubsurveyId));
-
-         subsurvey.datasetArr = datasetList.toArray(new Dataset[0]);
-         subsurveyList.add(subsurvey);
-
-         datasetList.clear();
-      }
-
-      return subsurveyList.toArray(new Subsurvey[0]);
-   }
-
-}
-
diff --git a/data-discovery/src/main/java/output/XmlSerializer.java b/data-discovery/src/main/java/output/XmlSerializer.java
deleted file mode 100644
index b0f692841037c48e9df6d3b7bf51f3d2bb388165..0000000000000000000000000000000000000000
--- a/data-discovery/src/main/java/output/XmlSerializer.java
+++ /dev/null
@@ -1,475 +0,0 @@
-
-import java.io.PrintWriter;
-
-// VOTable
-import uk.ac.starlink.table.*;// StarTable needed
-import uk.ac.starlink.votable.*;// Writer needed
-
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.io.BufferedWriter;
-
-import java.util.List;
-import java.util.ArrayList;
-
-import vo.parameter.*;
-
-final class XmlSerializer
-{
-   private XmlSerializer() {}
-
-   // VOTable
-
-   public static void serializeToVoTable(
-         PrintWriter writer, String charEncoding,
-         SearchOutputData searchOutputData,
-         String cutoutUrl, String mergeUrl,
-         boolean showDuration, long startTime_msec) throws IOException
-   {
-      StarTable dstable = makeSearchResultsTable( searchOutputData.subsurveyArr );
-
-      dstable.setParameter(new DescribedValue(
-               new DefaultValueInfo( "subsurveyCount", Integer.class, "Count of subsurveys with found datacube(s)" ),
-               searchOutputData.subsurveyArr.length ) );
-
-      dstable.setParameter(new DescribedValue(
-               new DefaultValueInfo( "datacubeCount",  Integer.class, "Count of all datacubes from VLKB-search" ),
-               searchOutputData.datacubeCount ) );
-
-      BufferedWriter out = new BufferedWriter( writer );
-
-      out.write("<?xml-stylesheet type='text/xsl' href='VOTable2XHTML.xsl'?>");
-      out.write( "<VOTABLE version='1.1'>" );
-      out.write( "<RESOURCE type=\"results\">" );
-      out.write( "<DESCRIPTION> " + searchOutputData.versionString + " </DESCRIPTION>" );
-
-      VOSerializer.makeSerializer( DataFormat.TABLEDATA, dstable ).writeInlineTableElement( out );
-
-      out.write( "</RESOURCE>" );
-
-      out.write( "<RESOURCE type=\"meta\" utype=\"adhoc:service\" name=\"CutoutService\" >" );
-      out.write( "<DESCRIPTION> VLKB cutout service </DESCRIPTION>");
-      out.write( "<PARAM name=\"standardID\" datatype=\"char\" arraysize=\"*\" value=\"ivo://ivoa.net/std/SODA#sync-1.0\" />");
-      out.write( "<PARAM name=\"accessURL\" datatype=\"char\" arraysize=\"*\" value=\"" + cutoutUrl +  "\" />");
-      out.write( "<GROUP name=\"inputParams\">");
-      out.write( "<PARAM name=\"ID\" datatype=\"char\" arraysize=\"*\" value=\"\" ref=\"primaryID\" />"); // FIXME results table obs_publisher_did FIELD must have attrib: ID="primaryID"
-      out.write( "</GROUP>");
-      out.write( "</RESOURCE>" );
-
-      out.write( "</VOTABLE>" );
-      out.flush();
-   }
-
-   private static StarTable makeSearchResultsTable(Subsurvey[] ssurv)
-   {
-      RowListStarTable astro = new RowListStarTable( ObscoreExt.OBSCORE_VLKB_SUBSURVEY_COLINFO );
-
-      for(Subsurvey subsurvey : ssurv)
-      {
-         for(Dataset dataset : subsurvey.datasetArr)
-         {
-            if(dataset.obsCore == null) continue; // FIXME skip mergeable datasets
-
-            astro.addRow( ObscoreExt.obscoreVlkbSubsurveyRow(dataset, subsurvey) );
-         }
-      }
-
-      return astro;
-   }
-
-
-   public static void serializeToVoTableBySubsurveys(
-         PrintWriter writer, String charEncoding,
-         SearchOutputData searchOutputData,
-         boolean showDuration, long startTime_msec) throws IOException
-   {
-      StarTable[] tables = makeSubsurveyTables( searchOutputData.subsurveyArr );
-      BufferedWriter out = new BufferedWriter( writer );
-
-      out.write( "<VOTABLE version='1.1'>" );
-      out.write( "<RESOURCE>" );
-      out.write( "<DESCRIPTION> " + searchOutputData.versionString + " </DESCRIPTION>" );
-      for ( int i = 0; i < tables.length; i++ )
-      {
-         VOSerializer.makeSerializer( DataFormat.TABLEDATA, tables[ i ] ).writeInlineTableElement( out );
-      }
-      out.write( "</RESOURCE>" );
-      out.write( "</VOTABLE>" );
-      out.flush();
-   } 
-
-   private static StarTable[] makeSubsurveyTables(Subsurvey[] subsurveyArr)
-   {
-      StarTable[] ssurvTableArr = new StarTable[subsurveyArr.length]; 
-      int ix = 0;
-
-      for(Subsurvey subsurvey : subsurveyArr)
-      {
-         RowListStarTable table = new RowListStarTable( ObscoreExt.OBSCORE_VLKB_COLINFO );
-
-         table.setParameter(new DescribedValue(
-                  new DefaultValueInfo( "subsurveyCount", Integer.class, "Count of subsurveys with found datacube(s)" ),
-                  subsurveyArr.length ) );
-
-         table.setParameter(new DescribedValue(
-                  new DefaultValueInfo( "datacubeCount",  Integer.class, "Count of all datacubes from VLKB-search" ),
-                  subsurvey.datasetArr.length ) );
-
-         table.setParameter(new DescribedValue(
-                  new DefaultValueInfo( "description",  String.class, "Reference description" ),
-                  subsurvey.description ) );
-
-         table.setParameter(new DescribedValue(
-                  new DefaultValueInfo( "survey",  String.class, "Survey name" ),
-                  subsurvey.surveyname ) );
-
-         table.setParameter(new DescribedValue(
-                  new DefaultValueInfo( "species",  String.class, "Species" ),
-                  subsurvey.species ) );
-
-         table.setParameter(new DescribedValue(
-                  new DefaultValueInfo( "transition",  String.class, "Transition" ),
-                  subsurvey.transition ) );
-
-         table.setParameter(new DescribedValue(
-                  new DefaultValueInfo( "frequency",  Double.class, "Frequency" ),
-                  subsurvey.rf ) );
-
-         for(Dataset dataset : subsurvey.datasetArr)
-         {
-            if(dataset.obsCore == null) continue; // FIXME skip mergeable datasets
-
-            table.addRow( ObscoreExt.obscoreVlkbRow(dataset) );
-         }
-
-         ssurvTableArr[ix++] = table;
-      }
-
-      return ssurvTableArr;
-   }
-
-
-
-
-   public static void serializeToVoTableBySurveys(
-         PrintWriter writer, String charEncoding,
-         SearchOutputData searchOutputData,
-         boolean showDuration, long startTime_msec) throws IOException
-   {
-      BufferedWriter out = new BufferedWriter( writer );
-
-      out.write( "<VOTABLE version='1.1'>" );
-      out.write( "<DESCRIPTION> " + searchOutputData.versionString + 
-             " subsurvey count: " + searchOutputData.subsurveyArr.length + " </DESCRIPTION>" );
-
-      if((searchOutputData.subsurveyArr != null) && (searchOutputData.subsurveyArr.length > 0))
-      {
-         Subsurvey[] subsurv = searchOutputData.subsurveyArr;
-
-         // assumes ORDERED subsurveyArray: by surveyname
-
-         List<StarTable> tableList = new ArrayList();
-         String prevSurveyname, prevDescription;// = subsurv[0].surveyname.trim();
-         int ix = 0;
-
-         do
-         {
-            prevSurveyname = subsurv[ix].surveyname.trim();
-            prevDescription = subsurv[ix].description.trim();
-
-            do
-            {
-               StarTable table = makeSubsurveyTable( subsurv[ix] );
-               tableList.add(table);
-               ix++;
-            }
-            while((ix < subsurv.length) && prevSurveyname.equals(subsurv[ix].surveyname.trim()));
-
-            StarTable[] tables = tableList.toArray(new StarTable[0]);
-            writeResourceSurvey(out, prevSurveyname, prevDescription, tables);
-            tableList.clear();
-         }
-         while(ix < subsurv.length);
-
-      }
-      out.write( "</VOTABLE>" );
-      out.flush();
-   }
-
-   private static void writeResourceSurvey(BufferedWriter out, String name, String description,
-         StarTable[] tables) throws IOException
-      {
-         out.write( "<RESOURCE name=\"" + name + "\">" );
-         out.write( "<DESCRIPTION> " + description.replaceAll("&", "&amp;") + " </DESCRIPTION>" );
-
-         /* PLACEHOLDER FOR RESOURCE PARAM
-
-            table.setParameter(new DescribedValue(
-            new DefaultValueInfo("subsurveyCount",Integer.class,
-            "Count of subsurveys with found datacube(s)" ),
-            subsurveyArr.length ) );
-
-            table.setParameter(new DescribedValue(
-            new DefaultValueInfo( "survey",  String.class,
-            "Survey name" ),
-            subsurvey.surveyname ) );
-
-*/
-         for ( int i = 0; i < tables.length; i++ )
-         {
-            VOSerializer.makeSerializer( DataFormat.TABLEDATA, tables[i] ).writeInlineTableElement( out );
-         }
-
-         out.write( "</RESOURCE>" );
-      }
-
-
-
-   private static StarTable makeSubsurveyTable(Subsurvey subsurvey)
-   {
-      RowListStarTable table = new RowListStarTable( ObscoreExt.OBSCORE_VLKB_COLINFO );
-
-      table.setParameter(new DescribedValue(
-               new DefaultValueInfo("datacubeCount", Integer.class, "Count of all datacubes from VLKB-search" ),
-               subsurvey.datasetArr.length ) );
-
-/*      table.setParameter(new DescribedValue(
-               new DefaultValueInfo( "velocity_unit",  String.class, "Unit of velocity in FITS header" ),
-               subsurvey.vel_unit ) );
-
-      table.setParameter(new DescribedValue(
-               new DefaultValueInfo( "survey",  String.class, "Survey name" ),
-               subsurvey.surveyname ) );
-*/
-      table.setParameter(new DescribedValue(
-               new DefaultValueInfo( "species",  String.class, "Species" ),
-               subsurvey.species ) );
-
-      table.setParameter(new DescribedValue(
-               new DefaultValueInfo( "transition",  String.class, "Transition" ),
-               subsurvey.transition ) );
-
-      table.setParameter(new DescribedValue(
-               new DefaultValueInfo( "frequency",  Double.class, "Frequency" ),
-               subsurvey.rf ) );
-
-/*      table.setParameter(new DescribedValue(
-               new DefaultValueInfo( "description",  String.class, "Reference description" ),
-               subsurvey.description ) );
-*/
-      for(Dataset dataset : subsurvey.datasetArr)
-      {
-         if(dataset.obsCore == null) continue; // FIXME skip mergeable datasets
-
-         table.addRow( ObscoreExt.obscoreVlkbRow(dataset) );
-      }
-
-      return table;
-   }
-
-
-   // legacy
-
-   public static void serializeToLegacyResults(
-         PrintWriter writer, String charEncoding,
-         AuthPolicy inputAuth, Pos pos, Band band, SubsurveyId inputSubsurveyId,
-         SearchOutputData searchOutputData,
-         boolean showDuration, long startTime_msec)
-   {
-      writer.println("<?xml version=\"1.0\" encoding=\"" + charEncoding + "\" standalone=\"yes\"?>");
-      writer.println("<results>");
-      writer.println("<description> " + searchOutputData.description + " </description>");
-
-      writer.println("<inputs>");
-      if(inputSubsurveyId != null) writer.println(serialize(inputSubsurveyId));
-      if(pos       != null) writer.println(serialize(pos));
-      if(band       != null) writer.println(serialize(band));
-      if(inputAuth        != null) writer.println(serialize(inputAuth));
-      writer.println("</inputs>");
-
-      writer.println("<msg> " + searchOutputData.versionString + " </msg>");
-      writer.println("<DatacubeCount> " + searchOutputData.datacubeCount + " </DatacubeCount>");
-      for(Subsurvey subsurvey : searchOutputData.subsurveyArr)
-      {
-         serialize(writer, subsurvey);
-      }
-      if(showDuration)
-         writer.println("<duration unit=\"msec\">" + (System.currentTimeMillis() - startTime_msec) + "</duration>");
-      writer.println("</results>");
-   }
-
-   private static String serialize(Pos pos)
-   {
-      StringBuilder xml = new StringBuilder();
-      if(pos != null)
-      {
-         xml.append("<SkySystem>"+pos.system+"</SkySystem>");
-         switch(pos.shape)
-         {
-            case CIRCLE:
-               xml.append("<l>" + String.valueOf(pos.circle.lon) + "</l>");
-               xml.append("<b>" + String.valueOf(pos.circle.lat) + "</b>");
-               xml.append("<r>" + String.valueOf(pos.circle.radius)+"</r>");
-               break;
-            case RANGE:
-               xml.append("<l>" + String.valueOf((pos.range.lon1 + pos.range.lon2)/2.0) + "</l>");
-               xml.append("<b>" + String.valueOf((pos.range.lat1 + pos.range.lat2)/2.0) + "</b>");
-               xml.append("<dl>" + String.valueOf(pos.range.lon2 - pos.range.lon1) + "</dl>");
-               xml.append("<db>" + String.valueOf(pos.range.lat2 - pos.range.lat1) + "</db>");
-               break;
-            default: // POLYGON was not used in VLKB-legacy -> let it fail with error
-               xml.append("<shape> unknown shape: " + pos.shape + " </shape>");
-         }
-      }
-      return xml.toString();
-   }
-
-   private static String serialize(Band band)
-   {
-      StringBuilder xml = new StringBuilder();
-      if(band != null)
-      {
-         xml.append("<vl>"   + String.valueOf(band.getMin())  +"</vl>");
-         xml.append("<vu>"   + String.valueOf(band.getMax())   +"</vu>");
-         xml.append("<vtype>"+ band.system                 +"</vtype>");
-      }
-
-      return xml.toString();
-   }
-
-   private static String serialize(SubsurveyId subsurveyId)
-   {
-      StringBuilder xml = new StringBuilder();
-      if(subsurveyId.surveyName  != null) xml.append("<SurveyName>"+subsurveyId.surveyName+"</SurveyName>");
-      if(subsurveyId.species     != null) xml.append("<Species>"+subsurveyId.species+"</Species>");
-      if(subsurveyId.transition  != null) xml.append("<Transition>"+subsurveyId.transition+"</Transition>");
-      return xml.toString();
-   }
-
-   private static String serialize(AuthPolicy auth)
-   {
-      StringBuilder xml = new StringBuilder();
-      xml.append("<AccessPolicy>" + auth.getAccessPolicy() + "</AccessPolicy>");
-      String ug = auth.getUserGroupsAsString(" ");
-      if(auth.getUserName() != null) xml.append("<UserName>" + auth.getUserName() + "</UserName>");
-      if(ug            != null) xml.append("<GroupNames>" + ug + "</GroupNames>");
-      return xml.toString();
-   }
-
-
-   private static void serialize(PrintWriter writer, Subsurvey subsurvey)
-   {
-      writer.println("<survey>");
-      // replace with escape the XML-predefined entities:
-      // <, >, &, %
-      if(subsurvey.description != null)
-      {
-         subsurvey.description = subsurvey.description.replace("&","&amp;");
-         subsurvey.description = subsurvey.description.replace("<","&lt;");
-         subsurvey.description = subsurvey.description.replace(">","&gt;");
-         subsurvey.description = subsurvey.description.replace("%","&#37;");
-      }
-
-      writer.println("<Description>"  + subsurvey.description + "</Description>");
-      writer.println("<Survey>"       + subsurvey.surveyname  + "</Survey>");
-      writer.println("<Species>"      + subsurvey.species     + "</Species>");
-      writer.println("<Transition>"   + subsurvey.transition  + "</Transition>");
-      writer.println("<RestFreq>");
-      writer.println("<value>"        + subsurvey.rf + "</value>");
-      writer.println("<unit>"         + "Hz"             + "</unit>"); // FIXME why was this needed? checj survey_populate,csv
-      writer.println("</RestFreq>");
-      writer.println("<VelocityUnit>" + subsurvey.vel_unit + "</VelocityUnit>");
-
-      for(Dataset dataset : subsurvey.datasetArr)
-      {
-         writer.println(serialize(dataset));
-      }
-      writer.println("</survey>");
-   }
-
-   private static String serialize(Dataset.Access access)
-   {
-      StringBuilder xml = new StringBuilder();
-
-      xml.append("<Access>");
-
-      if(access.accessFileUrl != null)
-         xml.append("<URL type=\"file\">" + access.accessFileUrl + "</URL>");
-
-      if(access.accessCutoutUrl != null)
-         xml.append("<URL type=\"cutout\">" + access.accessCutoutUrl.replaceAll("&","&amp;") + "</URL>");
-
-      if(access.accessMosaicUrl != null)
-         xml.append("<URL type=\"mosaic\">" + access.accessMosaicUrl.replaceAll("&","&amp;") + "</URL>");
-
-      xml.append("</Access>");
-
-      return xml.toString();
-   }
-
-   private static String serialize(Dataset.Vertices vertices)
-   {
-      StringBuilder xml = new StringBuilder();
-      xml.append("<vertices>");
-      xml.append("<SkyCoordSystem>");
-      for(int ix = 0; ix < vertices.VERT_COUNT; ix++)
-      {
-         xml.append("<P" + (ix+1) + ">");
-         xml.append("<longitude>" + vertices.lon[ix] + "</longitude>");
-         xml.append("<latitude>"  + vertices.lat[ix] + "</latitude>");
-         xml.append("</P" + (ix+1) + ">");
-      }
-      xml.append("</SkyCoordSystem>");
-      xml.append("</vertices>");
-
-      return xml.toString();
-   }
-
-
-   private static String serialize(Dataset dataset)
-   {
-      StringBuilder xml = new StringBuilder();
-
-      xml.append("<datacube>");
-      xml.append(serializeOverlapCode("overlap", dataset.overlapCode));
-      if(dataset.overlapCodeSky > 0)
-      {
-         xml.append(serializeOverlapCode("overlapSky", dataset.overlapCodeSky));
-      }
-      if(dataset.overlapCodeVel > 0)
-      {
-         xml.append(serializeOverlapCode("overlapVelocity", dataset.overlapCodeVel));
-      }
-      xml.append("<DataType>" + dataset.dataType + "</DataType>");
-      xml.append("<PublisherDID>" + dataset.publisherDid + "</PublisherDID>");
-      xml.append(serialize(dataset.access));
-      xml.append(serialize(dataset.vertices_deg));
-      xml.append("</datacube>");
-
-      return xml.toString();
-   }
-
-
-   private static String serializeOverlapCode(String tagName, int ovCode)
-   {
-      final String[] overString =
-      {
-         "The check could not be performed because the input Region could not be mapped into the coordinate system of the datacube Region.",
-         "There is no overlap between the two Regions.",
-         "The datacube Region is completely inside the input Region.",
-         "The input Region is completely inside the datacube Region.",
-         "There is partial overlap between the two Regions.",
-         "The Regions are identical to within their uncertainties.",
-         "The input Region is the exact negation of the datacube Region to within their uncertainties."
-      };
-
-      StringBuilder xml = new StringBuilder();
-      xml.append("<" + tagName + ">");
-      xml.append("<description>" + ( ((ovCode>=0) && (ovCode<=6)) ? overString[ovCode] : (" ovCode out-of-range: "+ Integer.toString(ovCode)) ) + "</description>");
-      xml.append("<code>"        + ovCode             + "</code>");
-      xml.append("</" + tagName + ">");
-      return xml.toString();
-   }
-
-
-}
diff --git a/data-discovery/src/main/java/webapi/MonitorFilter.java b/data-discovery/src/main/java/webapi/MonitorFilter.java
deleted file mode 100644
index 721e4de77a8f4cd3fab06e18c49044f763a6d571..0000000000000000000000000000000000000000
--- a/data-discovery/src/main/java/webapi/MonitorFilter.java
+++ /dev/null
@@ -1,50 +0,0 @@
-
-//import it.inaf.ia2.aa.data.User;
-
-import java.io.IOException;
-import java.util.*; // ArrayList<String>
-
-import java.util.logging.Logger;
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-
-import javax.servlet.http.HttpServletRequestWrapper;
-import java.security.Principal;
-
-
-public class MonitorFilter implements Filter
-{
-  private static final Logger LOGGER = Logger.getLogger(MonitorFilter.class.getName());
-
-   @Override
-   public void init(FilterConfig fc) throws ServletException {}
-
-   @Override
-   public void destroy() {}
-
-   @Override
-   public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
-                   throws IOException, ServletException
-   {
-        HttpServletRequest  request  = (HttpServletRequest)  req;
-        HttpServletResponse response = (HttpServletResponse) res;
-
-	LOGGER.info("before doFilter");
-
-        chain.doFilter(request, response);
-	
-	LOGGER.info("after  doFilter");
-
-   }
-
-
-
-}
-
diff --git a/data-discovery/src/main/java/webapi/SearchServlet.java b/data-discovery/src/main/java/webapi/SearchServlet.java
index 58d49d42134de8169118b504e27f45ea8f7c2e8e..7f510fb3dded4be91052b05906c7c5f8151c72a1 100644
--- a/data-discovery/src/main/java/webapi/SearchServlet.java
+++ b/data-discovery/src/main/java/webapi/SearchServlet.java
@@ -64,11 +64,8 @@ public class SearchServlet extends javax.servlet.http.HttpServlet
       {
          Map<String, String[]> params = request.getParameterMap();
 
-         // VLKB-legacy init
-         //Coord       coord = new Coord(params);
          SubsurveyId subsurveyId = new SubsurveyId(params);
 
-
          Coord coord = new Coord();
          coord.pos  = Pos.parsePos(params, DEFAULT_SKY_SYSTEM);
          coord.band = Band.parseBand(params, DEFAULT_SPEC_SYSTEM);
diff --git a/data-discovery/src/main/java/webapi/AuthorizationResponseFilter.java b/data-discovery/src/main/java/webapi/authzfilter/AuthorizationResponseFilter.java
similarity index 100%
rename from data-discovery/src/main/java/webapi/AuthorizationResponseFilter.java
rename to data-discovery/src/main/java/webapi/authzfilter/AuthorizationResponseFilter.java
diff --git a/data-discovery/src/main/java/webapi/AuthorizationResponseSettings.java b/data-discovery/src/main/java/webapi/authzfilter/AuthorizationResponseSettings.java
similarity index 100%
rename from data-discovery/src/main/java/webapi/AuthorizationResponseSettings.java
rename to data-discovery/src/main/java/webapi/authzfilter/AuthorizationResponseSettings.java
diff --git a/data-discovery/src/main/java/output/Dataset.java b/data-discovery/src/main/java/webapi/formatfilter/Dataset.java
similarity index 91%
rename from data-discovery/src/main/java/output/Dataset.java
rename to data-discovery/src/main/java/webapi/formatfilter/Dataset.java
index d3aec3953af9c1044770ad6d64bc44d9e197e2cc..da87a712b02e6dbf6ad96ebfe00e2f2fd3fb124c 100644
--- a/data-discovery/src/main/java/output/Dataset.java
+++ b/data-discovery/src/main/java/webapi/formatfilter/Dataset.java
@@ -26,7 +26,6 @@ class Dataset
    }
 
 
-   String subsurvey_id;
    int overlapCodeSky;
    int overlapCodeVel;
    int overlapCode;
@@ -59,10 +58,9 @@ class Dataset
    }
 
 
-   public Dataset(List<Dataset> datasetList, /*SubsurveyId subsurveyId,*/ String mergeUrlRoot, String mergeQueryString)
+   public Dataset(List<Dataset> datasetList, String mergeUrlRoot, String mergeQueryString)
    {
-      this.subsurvey_id = datasetList.get(0).subsurvey_id; // mergeabiity condition is more then 1 element in list
-      this.overlapCode  = 5; // 5: exact match --> legacy used 0 here FIXME 5 will not be correct on edges of Subsurvey coverage
+      this.overlapCode  = 5; // 5: exact match --> legacy used 0 here FIXME 5 will not be correct on edges of subsurvey-coverage
       this.publisherDid = mergePublisherDids(datasetList);
       this.dataType     = datasetList.get(0).dataType;
 
diff --git a/data-discovery/src/main/java/webapi/FormatResponseFilter.java b/data-discovery/src/main/java/webapi/formatfilter/FormatResponseFilter.java
similarity index 81%
rename from data-discovery/src/main/java/webapi/FormatResponseFilter.java
rename to data-discovery/src/main/java/webapi/formatfilter/FormatResponseFilter.java
index 7c01c846a31bea0139d17a1910ad4d15acd0d42c..dd9163012d5a835b6d9986075c5a2f62b4ea0043 100644
--- a/data-discovery/src/main/java/webapi/FormatResponseFilter.java
+++ b/data-discovery/src/main/java/webapi/formatfilter/FormatResponseFilter.java
@@ -27,8 +27,6 @@ class FormatResponseWrapper extends HttpServletResponseWrapper
 
    String[] pubdidArr;
 
-
-
    public FormatResponseWrapper(HttpServletResponse response)
    {
       super(response);
@@ -61,7 +59,6 @@ public class FormatResponseFilter implements Filter
    final String DEFAULT_SPEC_SYSTEM    = settings.defaults.specSystem;
    final String DEFAULT_TIME_SYSTEM = "MJD_UTC"; // FIXME take from confif file
 
-   protected Subsurvey[] dbSubsurveyArr  = null;
    private String reqQueryString;
 
    protected void doUsageError(String message, PrintWriter printWriter)
@@ -76,9 +73,6 @@ public class FormatResponseFilter implements Filter
       LOGGER.info("trace");
 
       String surveysAbsPathname = settings.serviceUrls.surveysAbsPathname();
-      LOGGER.info("Loading metadata from: " + surveysAbsPathname);
-      dbSubsurveyArr = Subsurvey.loadSubsurveys(surveysAbsPathname);
-      LOGGER.info("Surveys: loaded metadata for " + dbSubsurveyArr.length + " known surveys");
       LOGGER.info("Default charset: " + Charset.defaultCharset());
       LOGGER.info("DB: " + settings.dbConn.toString());
    }
@@ -87,7 +81,7 @@ public class FormatResponseFilter implements Filter
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
    {
-      LOGGER.info("trace");
+      LOGGER.fine("trace");
       LOGGER.info("REQUEST START =============================================================================================");
       long startTime_msec = System.currentTimeMillis();
 
@@ -103,71 +97,48 @@ public class FormatResponseFilter implements Filter
 
          // VLKB: reconstruct cutout/merge queryStrings and overlap code
          Map<String, String[]> params = request.getParameterMap();
-         SubsurveyId subsurveyId = new SubsurveyId(params); // for legacy-only Input
          Pos  pos  = Pos.parsePos(params, DEFAULT_SKY_SYSTEM);
          Band band = Band.parseBand(params, DEFAULT_SPEC_SYSTEM);
          String queryStringBase = toQueryString(pos, band);
 
-         ObsCore[] obsCoreArr = queryObsCore(pubdidArr,
-               pos); // VLKB: calc overlap-code for sky
+         // VLKB: calc overlap-code for sky
+         ObsCore[] obsCoreArr = queryObsCore(pubdidArr, pos);
 
-         Dataset[] datasetArr = convert(obsCoreArr,
-               band, // VLKB: calc overlap-code for velocity
+         // VLKB: calc overlap-code for velocity
+         // convert overlap-codes and adds access-urls for cutout, merge
+         Dataset[] datasetArr = convert(obsCoreArr, band,
                settings.serviceUrls.cutoutUrl(),
                queryStringBase);
 
-         SearchOutputData searchOutputData = SearchOutputData.marshall(datasetArr,
-               /*subsurveyId,*/
-               dbSubsurveyArr,
-               settings.serviceUrls.mergeUrl(),
-               queryStringBase);
-
          String respFormat;
          String respFormatReq[] = params.get("RESPONSEFORMAT");
          if(respFormatReq != null && (respFormatReq.length > 0) && !respFormatReq[0].isEmpty())
          {
             respFormat = respFormatReq[0];
-            LOGGER.info("responseFormat(from request): " + respFormat);
+            LOGGER.finest("responseFormat(from request): " + respFormat);
          }
          else
          {
             respFormat = settings.serviceUrls.responseFormat();
-            LOGGER.info("responseFormat(from settings): " + respFormat);
+            LOGGER.finest("responseFormat(from settings): " + respFormat);
          }
 
          response.setCharacterEncoding(RESPONSE_ENCODING);
 
-         if(respFormat.equals("application/x-vlkb+xml"))
-         {
-            response.setContentType("application/xml");
-            boolean showDuration = true;
-            XmlSerializer.serializeToLegacyResults(responseWriter, RESPONSE_ENCODING,
-                  responseWrapper.auth, pos, band, subsurveyId, // <inputs/>
-                  searchOutputData,
-                  showDuration,startTime_msec);
-         }
-         else if(respFormat.startsWith("application/x-votable+xml"))
+         if(respFormat.startsWith("application/x-votable+xml"))
          {
             response.setContentType("application/xml");
             boolean showDuration = false;
 
-            if(respFormat.contains("mode=bysubsurveys"))
-               XmlSerializer.serializeToVoTableBySubsurveys(responseWriter, RESPONSE_ENCODING,
-                     searchOutputData,showDuration,startTime_msec);
-            else if(respFormat.contains("mode=bysurveys"))
-               XmlSerializer.serializeToVoTableBySurveys(responseWriter, RESPONSE_ENCODING,
-                     searchOutputData,showDuration,startTime_msec);
-            else
-               XmlSerializer.serializeToVoTable(responseWriter, RESPONSE_ENCODING,
-                     searchOutputData,
-                     settings.serviceUrls.cutoutUrl(),settings.serviceUrls.mergeUrl(),
-                     showDuration,startTime_msec);
-
+            XmlSerializer.serializeToVoTable(responseWriter, RESPONSE_ENCODING,
+                  datasetArr,
+                  settings.serviceUrls.cutoutUrl(),settings.serviceUrls.mergeUrl(),
+                  showDuration,startTime_msec);
          }
          else
          {
             final String errMsg = "Illegal response format request: " + respFormat;
-            LOGGER.info(errMsg);
+            LOGGER.warning(errMsg);
             response.setContentType("text/plain");
             doUsageError(errMsg, responseWriter);
             // FIXME set http err code 
@@ -177,7 +148,7 @@ public class FormatResponseFilter implements Filter
       }
       else
       {
-         LOGGER.info("SearchServlet returned no ID's.");
+         LOGGER.fine("SearchServlet returned no ID's.");
       }
 
       LOGGER.info("REQUEST END   =============================================================================================");
@@ -202,8 +173,11 @@ public class FormatResponseFilter implements Filter
       String dataproduct_type;
       Integer calib_level;
       String obs_collection;
+      String obs_title;
       String obs_id;
       String obs_publisher_did;
+      String bib_reference;
+      String data_rights;
 
       String access_url;
       String access_format;
@@ -271,7 +245,6 @@ public class FormatResponseFilter implements Filter
 
          dataset.obsCore = obsCore;
 
-         dataset.subsurvey_id   = obsCore.obs_collection;
          dataset.overlapCodeSky = convertToOverlapCodeSky(obsCore.inputInsideDb, obsCore.dbInsideInput);
          dataset.overlapCodeVel = convertToOverlapCodeVel(band,obsCore.em_valid,obsCore.em_min,obsCore.em_max);
          dataset.overlapCode    = convertToOverlapCode(dataset.overlapCodeSky, dataset.overlapCodeVel);
diff --git a/data-discovery/src/main/java/webapi/FormatResponseSettings.java b/data-discovery/src/main/java/webapi/formatfilter/FormatResponseSettings.java
similarity index 100%
rename from data-discovery/src/main/java/webapi/FormatResponseSettings.java
rename to data-discovery/src/main/java/webapi/formatfilter/FormatResponseSettings.java
diff --git a/data-discovery/src/main/java/output/ObscoreExt.java b/data-discovery/src/main/java/webapi/formatfilter/ObscoreExt.java
similarity index 71%
rename from data-discovery/src/main/java/output/ObscoreExt.java
rename to data-discovery/src/main/java/webapi/formatfilter/ObscoreExt.java
index 4027f4564f46dc63d9ed2b06b4cc597f2288fce4..f9be0fb4264e85401673060de5c99d1d0287aa3e 100644
--- a/data-discovery/src/main/java/output/ObscoreExt.java
+++ b/data-discovery/src/main/java/webapi/formatfilter/ObscoreExt.java
@@ -9,8 +9,11 @@ class ObscoreExt
       new ColumnInfo( "dataproduct_type",  String.class,  "Dataproduct Type (image|cube)" ),
       new ColumnInfo( "calib_level",       Integer.class, "Calibration level" ),
       new ColumnInfo( "obs_collection",    String.class,  "Collection" ),
+      new ColumnInfo( "obs_title",         String.class,  "Title" ),
       new ColumnInfo( "obs_id",            String.class,  "Observation Id" ),
       new ColumnInfo( "obs_publisher_did", String.class,  "Publisher Did" ),
+      new ColumnInfo( "bib_reference",     String.class,  "Bibbliographic refererence" ),
+      new ColumnInfo( "data_rights",       String.class,  "Data rights" ),
 
       new ColumnInfo( "access_url",     String.class, "Access URL" ),
       new ColumnInfo( "access_format",  String.class, "Format (MIME type)" ),
@@ -53,8 +56,11 @@ class ObscoreExt
          dataset.obsCore.dataproduct_type,//dataset.dataType, 
             Integer.valueOf( dataset.obsCore.calib_level ),
             dataset.obsCore.obs_collection,
+            dataset.obsCore.obs_title,
             dataset.obsCore.obs_id,
             dataset.obsCore.obs_publisher_did,
+            dataset.obsCore.bib_reference,
+            dataset.obsCore.data_rights,
 
             dataset.obsCore.access_url,//dataset.access.accessCutoutUrl,
             dataset.obsCore.access_format,
@@ -95,24 +101,10 @@ class ObscoreExt
       new ColumnInfo( "overlapSky",  Integer.class, "Overlap Code for Sky axes" ),
       new ColumnInfo( "overlapSpec", Integer.class, "Overlap Code for Spectral axis" ),
 
-/*      new ColumnInfo( "P1lon", Double.class, "P1 longitude" ),
-      new ColumnInfo( "P1lat", Double.class, "P1 latitude" ),
-      new ColumnInfo( "P2lon", Double.class, "P2 longitude" ),
-      new ColumnInfo( "P2lat", Double.class, "P2 latitude" ),
-      new ColumnInfo( "P3lon", Double.class, "P3 longitude" ),
-      new ColumnInfo( "P3lat", Double.class, "P3 latitude" ),
-      new ColumnInfo( "P4lon", Double.class, "P4 longitude" ),
-      new ColumnInfo( "P4lat", Double.class, "P4 latitude" ),
-*/
       new ColumnInfo( "s_region_galactic", String.class, "Region [GALACTIC]" ),
 
       new ColumnInfo( "vel_min",    Double.class, "Velocity min" ),
       new ColumnInfo( "vel_max",    Double.class, "Velocity max" ),
- 
-      new ColumnInfo( "file_url",   String.class, "Access URL: all file" ),
-      new ColumnInfo( "cutout_url", String.class, "Access URL: cut file" ),
-      new ColumnInfo( "merge_url",  String.class, "Access URL: demosaicing files" ),
-
    };
 
    public static Object[] vlkbRow( Dataset dataset )
@@ -123,42 +115,11 @@ class ObscoreExt
             Integer.valueOf( dataset.overlapCodeSky ),
             Integer.valueOf( dataset.overlapCodeVel ),
 
-/*            Double.valueOf(dataset.vertices_deg.lon[0]), Double.valueOf(dataset.vertices_deg.lat[0]),
-            Double.valueOf(dataset.vertices_deg.lon[1]), Double.valueOf(dataset.vertices_deg.lat[1]),
-            Double.valueOf(dataset.vertices_deg.lon[2]), Double.valueOf(dataset.vertices_deg.lat[2]),
-            Double.valueOf(dataset.vertices_deg.lon[3]), Double.valueOf(dataset.vertices_deg.lat[3]),
-*/
             dataset.obsCore.s_region_galactic,
 
             dataset.obsCore.vel_min == null ? null : Double.valueOf(dataset.obsCore.vel_min),
             dataset.obsCore.vel_min == null ? null : Double.valueOf(dataset.obsCore.vel_max),
 
-            dataset.access.accessFileUrl,
-            dataset.access.accessCutoutUrl,
-            dataset.access.accessMosaicUrl,
-      };
-   }
-
-
-
-   public static final ColumnInfo[] SUBSURVEY_COLINFO =
-   {
-      new ColumnInfo( "frequency",     Double.class, "Frequency" ),
-      new ColumnInfo( "survey",        String.class, "Survey name" ),
-      new ColumnInfo( "species",       String.class, "Species" ),
-      new ColumnInfo( "transition",    String.class, "Transition" ),
-      new ColumnInfo( "description",   String.class, "Descritpion" )
-   };
-
-   public static Object[] subsurveyRow( Subsurvey subsurvey )
-   {
-      return new Object[]
-      {
-         subsurvey.rf,
-            subsurvey.surveyname,
-            subsurvey.species,
-            subsurvey.transition,
-            subsurvey.description
       };
    }
 
@@ -172,15 +133,6 @@ class ObscoreExt
    }
 
 
-   public static final ColumnInfo[] OBSCORE_VLKB_SUBSURVEY_COLINFO = concat(concat(OBSCORE_COLINFO, VLKB_COLINFO), SUBSURVEY_COLINFO);
-
-   public static Object[] obscoreVlkbSubsurveyRow( Dataset dataset, Subsurvey subsurvey )
-   {
-      return concat(concat(obscoreRow(dataset),vlkbRow(dataset)), subsurveyRow(subsurvey));
-   }
-
-
-
 
    private static ColumnInfo[] concat(ColumnInfo[] arr1, ColumnInfo[] arr2)
    {
diff --git a/data-discovery/src/main/java/webapi/formatfilter/XmlSerializer.java b/data-discovery/src/main/java/webapi/formatfilter/XmlSerializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..6e36122cf89a555402f1d57daebc4bff261aae17
--- /dev/null
+++ b/data-discovery/src/main/java/webapi/formatfilter/XmlSerializer.java
@@ -0,0 +1,74 @@
+
+import java.io.PrintWriter;
+
+// VOTable
+import uk.ac.starlink.table.*;// StarTable needed
+import uk.ac.starlink.votable.*;// Writer needed
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.BufferedWriter;
+
+import java.util.List;
+import java.util.ArrayList;
+
+import vo.parameter.*;
+
+final class XmlSerializer
+{
+   private XmlSerializer() {}
+
+   // VOTable
+
+   public static void serializeToVoTable(
+         PrintWriter writer, String charEncoding,
+         Dataset[] datasetArr,
+         String cutoutUrl, String mergeUrl,
+         boolean showDuration, long startTime_msec) throws IOException
+   {
+      StarTable dstable = makeSearchResultsTable( datasetArr );
+
+      dstable.setParameter(new DescribedValue(
+               new DefaultValueInfo("datacubeCount", Integer.class, "Count of all datacubes from VLKB-search" ),
+               datasetArr.length ) );
+
+      BufferedWriter out = new BufferedWriter( writer );
+
+      out.write("<?xml-stylesheet type='text/xsl' href='VOTable2XHTML.xsl'?>");
+      out.write( "<VOTABLE version='1.1'>" );
+      out.write( "<RESOURCE type=\"results\">" );
+      out.write( "<DESCRIPTION> " + Version.asString + " </DESCRIPTION>" );
+
+      VOSerializer.makeSerializer( DataFormat.TABLEDATA, dstable ).writeInlineTableElement( out );
+
+      out.write( "</RESOURCE>" );
+
+      out.write( "<RESOURCE type=\"meta\" utype=\"adhoc:service\" name=\"CutoutService\" >" );
+      out.write( "<DESCRIPTION> VLKB cutout service </DESCRIPTION>");
+      out.write( "<PARAM name=\"standardID\" datatype=\"char\" arraysize=\"*\" value=\"ivo://ivoa.net/std/SODA#sync-1.0\" />");
+      out.write( "<PARAM name=\"accessURL\" datatype=\"char\" arraysize=\"*\" value=\"" + cutoutUrl +  "\" />");
+      out.write( "<GROUP name=\"inputParams\">");
+      out.write( "<PARAM name=\"ID\" datatype=\"char\" arraysize=\"*\" value=\"\" ref=\"primaryID\" />"); // FIXME results table obs_publisher_did FIELD must have attrib: ID="primaryID"
+      out.write( "</GROUP>");
+      out.write( "</RESOURCE>" );
+
+      out.write( "</VOTABLE>" );
+      out.flush();
+   }
+
+   private static StarTable makeSearchResultsTable(Dataset[] datasetArr)
+   {
+      RowListStarTable astro = new RowListStarTable( ObscoreExt.OBSCORE_VLKB_COLINFO );
+
+      for(Dataset dataset : datasetArr)
+      {
+         if(dataset.obsCore == null) continue; // FIXME skip mergeable datasets
+
+         astro.addRow( ObscoreExt.obscoreVlkbRow(dataset) );
+      }
+
+      return astro;
+   }
+
+
+}