diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..471af0d9476dfdd8f8b52fdbbc0f7a665ffa0812
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,22 @@
+
+VERSION ?= $(shell git describe)
+
+
+all:
+
+
+tgz:
+	cd .. \
+	&& ln -s vlkb-datasets vlkb-datasets-${VERSION} \
+	&& tar --exclude='.git' --exclude='rpm' --exclude='docker' -hczf vlkb-datasets-$(VERSION).tag.gz vlkb-datasets-$(VERSION) \
+	&& rm -f vlkb-datasets-$(VERSION) \
+	&& cd -
+
+war:
+	make -C data-discovery create-war && mv data-discovery/target/vlkb-search*.war .
+
+
+clean:
+	make -C data-discovery clean
+
+
diff --git a/README.md b/README.md
index f2b7c2acd1d2be56df73443a7fd7358452e5f62a..946f16b34bb1fe9dea1f4c15db20e199fc7fe8a9 100644
--- a/README.md
+++ b/README.md
@@ -1,93 +1,32 @@
-# vlkb-search
+## vlkb-search
 
+is a web-application to discover astronomical data stored in FITS-files and describe in a VO ObsCore table.
+It consists of
 
+- **vlkb-search** web-application and associated **vlkb-obscore** command line tool based on VO ObsCore table
 
-## Getting started
+The vlkb-obscore cli is available from https://ict.inaf.it/gitlab/butora/vlkb-datasets .
+Dockerized version of the web-app is available from https://ict.inaf.it/gitlab/butora/vlkb-datasets-docker .
 
-To make it easy for you to get started with GitLab, here's a list of recommended next steps.
-
-Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)!
-
-## Add your files
-
-- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files
-- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command:
-
-```
-cd existing_repo
-git remote add origin https://www.ict.inaf.it/gitlab/ViaLactea/vlkb-search.git
-git branch -M main
-git push -uf origin main
-```
-
-## Integrate with your tools
-
-- [ ] [Set up project integrations](https://www.ict.inaf.it/gitlab/ViaLactea/vlkb-search/-/settings/integrations)
-
-## Collaborate with your team
-
-- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/)
-- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html)
-- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically)
-- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/)
-- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html)
-
-## Test and Deploy
-
-Use the built-in continuous integration in GitLab.
-
-- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html)
-- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/)
-- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html)
-- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/)
-- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html)
-
-***
-
-# Editing this README
-
-When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template.
-
-## Suggestions for a good README
-
-Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
-
-## Name
-Choose a self-explaining name for your project.
-
-## Description
-Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors.
-
-## Badges
-On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge.
+## Installation
 
-## Visuals
-Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method.
+There are rpm, deb and war packages avaialable for Debian, CentOS and Fedora.
 
-## Installation
-Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection.
+### Install from packages (rpm/deb and war)
 
-## Usage
-Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README.
+There are two war-packages for the serch and cutout web-applications
 
-## Support
-Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc.
+- vlkb-search-X.Y.Z.war
 
-## Roadmap
-If you have ideas for releases in the future, it is a good idea to list them in the README.
+And the package for linux executable (deb or rpm)
 
-## Contributing
-State if you are open to contributions and what your requirements are for accepting them.
+- vlkb-obscore-X.Y.Z.deb is a optional tool to create ObsCore table for vlkb-search-\*.war
 
-For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self.
+To download version X.Y, add one of the above package names to
 
-You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.
+```bash
+curl -O --header "PRIVATE-TOKEN: <security-token>"  "https://ict.inaf.it/gitlab/api/v4/projects/79/packages/generic/vlkb-datasets/X.Y/vlkb-obscore-<version>.{deb|rpm}"
 
-## Authors and acknowledgment
-Show your appreciation to those who have contributed to the project.
+```
 
-## License
-For open source projects, say how it is licensed.
 
-## Project status
-If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers.
diff --git a/auth/Makefile b/auth/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..36c0b4b3ada259db9223bb0beab4ac31cc12d664
--- /dev/null
+++ b/auth/Makefile
@@ -0,0 +1,41 @@
+################################################################################
+LIB_DIR  = target/lib
+TARGET = $(LIB_DIR)/vlkb-auth.jar
+CLASS_DIR = target/classes
+VERSION ?= $(shell git describe)
+################################################################################
+EXT_LIB_DIR  = ../java-libs/lib
+################################################################################
+JC = javac
+JFLAGS = -g
+CLASSPATH = $(CLASS_DIR):$(EXT_LIB_DIR)/*
+################################################################################
+SRC_DIR = src/main/java:src/test/java
+SOURCES  = $(wildcard src/*Filter.java) src/main/java/AuthPolicy.java src/test/java/Main.java
+################################################################################
+
+all : build
+
+build : jar
+
+.PHONY: classes makedirs clean test
+
+makedirs:
+	mkdir -p $(CLASS_DIR) $(LIB_DIR)
+
+classes: makedirs
+	$(JC) $(JFLAGS) -cp $(CLASSPATH) -sourcepath $(SRC_DIR) -d $(CLASS_DIR) $(SOURCES)
+
+jar: classes
+	jar -cf $(TARGET) -C $(CLASS_DIR) .
+
+clean :
+	rm -fr $(CLASS_DIR) $(LIB_DIR)
+	rmdir target
+
+run-test:
+	java -cp $(CLASSPATH) Main ../test/token.base64
+
+test:
+	@echo "SOURCES  : "$(SOURCES)
+
diff --git a/auth/permissions-table.sql b/auth/permissions-table.sql
new file mode 100644
index 0000000000000000000000000000000000000000..8ac6fd17eee6e20105ba8a3097509e5716aac1a2
--- /dev/null
+++ b/auth/permissions-table.sql
@@ -0,0 +1,6 @@
+
+
+create table permissions ( obs_publisher_did varchar primary key, groups TEXT[] NULL );
+
+INSERT INTO permissions (obs_publisher_did, groups) VALUES ('ivo://auth.example.org/datasets/fits?cubes/part-Eridanus_full_image_V3.fits#0','{AllPrivate}');
+
diff --git a/auth/resources/Backup/auth.properties-AuthLibExample b/auth/resources/Backup/auth.properties-AuthLibExample
new file mode 100644
index 0000000000000000000000000000000000000000..d032f0cb0c9f8f50485e411524038de94b0982be
--- /dev/null
+++ b/auth/resources/Backup/auth.properties-AuthLibExample
@@ -0,0 +1,14 @@
+client_id=test
+client_secret=test-secret
+rap_uri=http://localhost/rap-ia2
+store_state_on_login_endpoint=true
+scope=openid read:userspace write:userspace read:fileserver write:fileserver read:gms read:rap
+
+gms_uri=http://localhost:8082/gms
+groups_autoload=false
+
+# default values
+access_token_endpoint=/auth/oauth2/token
+user_authorization_endpoint=/auth/oauth2/authorize
+check_token_endpoint=/auth/oauth2/token
+jwks_endpoint=/auth/oidc/jwks
diff --git a/auth/resources/Backup/shiro.ini b/auth/resources/Backup/shiro.ini
new file mode 100644
index 0000000000000000000000000000000000000000..4324a25031ec4eab8cdd1b1aa288afd17f3300eb
--- /dev/null
+++ b/auth/resources/Backup/shiro.ini
@@ -0,0 +1,40 @@
+#
+# Copyright (c) 2013 Les Hazlewood and contributors
+#
+# 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.
+#
+
+# INI configuration is very powerful and flexible, while still remaining succinct.
+# Please http://shiro.apache.org/configuration.html and
+# http://shiro.apache.org/web.html for more.
+
+[main]
+shiro.loginUrl = /login.jsp
+cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
+securityManager.cacheManager = $cacheManager
+#securityManager.realm = $stormpathRealm
+
+[users]
+# syntax: user = password , roles
+vialactea = ia2vlkb, ROLE_ADMIN
+
+[roles]
+ROLE_ADMIN = *
+
+[urls]
+#/login.jsp = authc
+/logout = logout
+/** = authcBasic
+#/ivoa/resources/basic/** = authcBasic
+#/ivoa/resources/full/** = authc
+
diff --git a/auth/resources/auth.properties b/auth/resources/auth.properties
new file mode 100644
index 0000000000000000000000000000000000000000..c9c8aee27f0017b03a10a17896236eae4a93a018
--- /dev/null
+++ b/auth/resources/auth.properties
@@ -0,0 +1,10 @@
+rap_uri=https://sso.ia2.inaf.it/rap-ia2
+gms_uri=https://sso.ia2.inaf.it/gms
+client_id=vospace_ui_demo
+client_secret=VOSpaceDemo123
+
+groups_autoload=true
+store_state_on_login_endpoint=true
+scope=openid email profile read:rap
+
+allow_anonymous_access=true
diff --git a/auth/resources/iamtoken.properties b/auth/resources/iamtoken.properties
new file mode 100644
index 0000000000000000000000000000000000000000..d275d68bee277ed3450eee1349d4a3a2c48210dc
--- /dev/null
+++ b/auth/resources/iamtoken.properties
@@ -0,0 +1,13 @@
+
+# certificates endpoint
+#jwks_url=
+introspect=
+client_name=
+client_password=
+
+# account created for the service
+resource_id=
+
+# username for non-authenticated requests
+non_authn_username=anonymous
+
diff --git a/auth/resources/neatoken.properties b/auth/resources/neatoken.properties
new file mode 100644
index 0000000000000000000000000000000000000000..839e15d714346acd080d3bc7474dc164e97a4af8
--- /dev/null
+++ b/auth/resources/neatoken.properties
@@ -0,0 +1,10 @@
+
+# certificates endpoint
+jwks_url=
+
+# account created for the service
+resource_id=
+
+# username for non-authenticated requests
+non_authn_username=anonymous
+
diff --git a/auth/src/main/java/AuthPolicy.java b/auth/src/main/java/AuthPolicy.java
new file mode 100644
index 0000000000000000000000000000000000000000..8c3d60f3b88d9899726e44c213a751d6cbb3a203
--- /dev/null
+++ b/auth/src/main/java/AuthPolicy.java
@@ -0,0 +1,338 @@
+
+// external inputs:
+// User data (name and list of groups)
+// List of PublisherDid's as param to some app-function (multi-cutout, merge)
+// Database connection (from Settings)
+
+// For Non-authenticated requests two behaviours possible:
+// A, setups with configured security: return only PUBLIC data
+// B, setups wihtout need of security: access all data
+// Currently B supported: Vlkb security filters will always set UsePrincipal.
+// Security filters could reserve 'anonymous' user for non-authenticated requests, if needed.
+// So missing UserPrincipal is interpreted as setup without security filters - full access allowed.
+
+
+import java.util.logging.Logger;
+
+import java.io.PrintWriter;
+import java.security.Principal;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Arrays;
+import java.util.ListIterator;
+
+
+
+public class AuthPolicy
+{
+   private static final Logger LOGGER = Logger.getLogger(AuthPolicy.class.getName());
+
+   enum Access { PUBLIC_ONLY, PUBLIC_AND_AUTHORIZED_PRIVATE };
+   private Access access;
+
+   private String   userName;
+   private String[] userGroups;
+   private boolean userGroupsValid;
+
+   private String dbConnUrl;
+   private String dbUserName;
+   private String dbPassword;
+
+
+   public AuthPolicy(String userName, String[] userGroups)
+   {
+      this.userName   = userName;
+      this.userGroups = userGroups;
+      this.userGroupsValid = true;
+
+      access = Access.PUBLIC_AND_AUTHORIZED_PRIVATE;
+
+      LOGGER.info("User [Groups]: " + userName + " [ " + String.join(" ", userGroups) + " ]" );
+   }
+
+
+
+
+   public AuthPolicy(Principal principal)
+   {
+      if(principal == null)
+      {
+         access = Access.PUBLIC_ONLY;
+         userName = null;
+         userGroups = null;
+         userGroupsValid = false;
+         LOGGER.info("Non authenticated request (UserPrincipal null in HttpServletRequest)");
+      }
+      else
+      {
+         if(principal instanceof VlkbUser)
+         {
+            VlkbUser vlkbUser = (VlkbUser) principal;
+
+            userName   = vlkbUser.getName();
+            userGroups = vlkbUser.getGroupsAsArray();
+            userGroupsValid = true;
+
+            access = Access.PUBLIC_AND_AUTHORIZED_PRIVATE;
+
+            LOGGER.info("User [Groups]: " + userName + " [ " + String.join(" ", userGroups) + " ]" );
+         }
+         else
+         {
+            userName = principal.getName();
+            LOGGER.info("DBG principal not instance of VlkbUser, but has user-name: " + userName);
+            userGroups = new String[]{""};//{"VLKB.groupA", "AllPrivate"}; // was for shiro
+            userGroupsValid = true;
+            access = Access.PUBLIC_AND_AUTHORIZED_PRIVATE;
+            //throw new IllegalArgumentException("UserPrincipal is not of expected type");
+         }
+      }
+   }
+
+
+
+   public String getUserName()
+   {
+      return userName;
+   }
+
+   public boolean getUserGroupsValid()
+   {
+      return userGroupsValid;
+   }
+
+
+   public String[] getUserGroups()
+   {
+      return userGroups;
+   }
+
+   public String getUserGroupsSqlFormat()
+   {
+      if( (userGroups != null) && (userGroups.length > 0) )
+      {
+         return "\"" + String.join("\",\"" , userGroups) + "\"";
+      }
+      else
+      {
+         return null;
+      }
+   }
+
+   public String getUserGroupsAsString(String separator)
+   {
+      if( (userGroups != null) && (userGroups.length > 0) )
+      {
+         return String.join(separator, userGroups);
+      }
+      else
+      {
+         return null;
+      }
+   }
+
+
+
+
+   public String getAccessPolicy()
+   {
+      return access.name(); // returns enum as string
+   }
+
+
+
+   public void toXML(PrintWriter writer)
+   {
+      writer.println("<AccessPolicy>" + this.getAccessPolicy() + "</AccessPolicy>");
+      String ug = getUserGroupsAsString(" ");
+      if(userName   != null) writer.println("<UserName>" + userName + "</UserName>");
+      if(ug         != null) writer.println("<GroupNames>" + ug + "</GroupNames>");
+   }
+
+
+
+   public String[] filterAuthorized(String[] pubdidArr, String dbConnUrl, String dbUserName, String dbPassword)
+   {
+      this.dbConnUrl = dbConnUrl;
+      this.dbUserName = dbUserName;
+      this.dbPassword = dbPassword;
+
+      LOGGER.info("with String[] trace");
+      return filterAuthorized(new ArrayList<String>(Arrays.asList(pubdidArr)), dbConnUrl);
+   }
+
+   private String[] filterAuthorized(ArrayList<String> pubdidList, String dbConnUrl)
+   {
+      //LOGGER.info("with List <String> trace");
+      switch(access)
+      {
+         case PUBLIC_ONLY :
+            filterNotPublic(pubdidList, dbConnUrl);
+            break;
+
+         case PUBLIC_AND_AUTHORIZED_PRIVATE :
+            filterNotAuthorized(pubdidList, dbConnUrl);
+            break;
+
+         default :
+            assert false : "Unrecoginzed  access : " + access;
+      }
+      return pubdidList.toArray(new String[0]); 
+   }
+
+
+   private void filterNotPublic(ArrayList<String> pubdids, String dbConnUrl)
+   {
+      LOGGER.info("trace");
+      assert pubdids != null;
+      //LOGGER.info("PublisherDID list original : " + String.join(" ", pubdids));
+
+      List<AuthPolicyDb.PubdidGroups> privateUniqPubdids = db_queryPrivateUniqPubdidGroups(dbConnUrl, pubdids);
+      List<String> notAuthorizedUniqPubdids = pubdidsNotPublic(privateUniqPubdids, userGroups);
+
+      LOGGER.info("AuthZ removes: " + String.join(" ", notAuthorizedUniqPubdids));
+
+      removeNotAuthorized(pubdids, notAuthorizedUniqPubdids);
+
+      //LOGGER.info("PublisherDID list filtered : " + (pubdids.isEmpty() ? "" : String.join(" ", pubdids)));
+   }
+
+
+   private List<String> pubdidsNotPublic(List<AuthPolicyDb.PubdidGroups> pubdidList, String[] userGroups)
+   {
+      LOGGER.info("trace");
+      //LOGGER.info("userGroups: " + String.join(" ",userGroups));
+
+      List<String> pubdidsNotAuthorizedList = new LinkedList<String>();
+      ListIterator<AuthPolicyDb.PubdidGroups> it = pubdidList.listIterator();
+
+      while (it.hasNext())
+      {
+         AuthPolicyDb.PubdidGroups pubdidGroups = it.next();
+
+         //LOGGER.info(pubdidGroups.pubdid + " : " + String.join(" ",pubdidGroups.groups));
+
+         if( true )// isIntersectionEmpty(pubdidGroups.groups, userGroups) )
+         {
+            pubdidsNotAuthorizedList.add(pubdidGroups.pubdid);
+         }
+      }
+
+      return pubdidsNotAuthorizedList;
+   }
+
+
+
+   private void filterNotAuthorized(ArrayList<String> pubdids, String dbConnUrl)
+   {
+      LOGGER.info("trace");
+      assert pubdids != null;
+      //LOGGER.info("PublisherDID list original : " + String.join(" ", pubdids));
+
+      List<AuthPolicyDb.PubdidGroups> privateUniqPubdids = db_queryPrivateUniqPubdidGroups(dbConnUrl, pubdids);
+      List<String> notAuthorizedUniqPubdids = pubdidsNotAuthorized(privateUniqPubdids, userGroups);
+
+      LOGGER.info("AuthZ removes: " + String.join(" ", notAuthorizedUniqPubdids));
+
+      removeNotAuthorized(pubdids, notAuthorizedUniqPubdids);
+
+      //LOGGER.info("PublisherDID list filtered : " + (pubdids.isEmpty() ? "" : String.join(" ", pubdids)));
+   }
+
+
+
+   private void removeNotAuthorized(ArrayList<String> pubdids, List<String> notAuthorizedUniqPubdids)
+   {
+      ListIterator<String> itr = pubdids.listIterator();
+      while (itr.hasNext())
+      {
+         String pubdid = itr.next();
+
+         for(String notAuthPubdid : notAuthorizedUniqPubdids)
+         {
+            if (pubdid.equals(notAuthPubdid)) itr.remove();
+         }
+      }
+
+      return;
+   }
+
+
+
+   private List<AuthPolicyDb.PubdidGroups> db_queryPrivateUniqPubdidGroups(String dbConnUrl, List<String> pubdids)
+   {
+      AuthPolicyDb adb;
+      synchronized(AuthPolicyDb.class)
+      {
+         AuthPolicyDb.dbConnUrl  = this.dbConnUrl;
+         AuthPolicyDb.dbUserName = this.dbUserName;
+         AuthPolicyDb.dbPassword = this.dbPassword;
+
+         adb = new AuthPolicyDb();
+      }
+
+      Set<String> uniqPubdids = new HashSet<String>(pubdids);
+
+      if(uniqPubdids.isEmpty())
+      {
+         List<AuthPolicyDb.PubdidGroups> privatePubdidGroups = Collections.emptyList();
+         return privatePubdidGroups;
+      }
+      else
+      {
+         // FIXME handle DB-exceptions
+         List<AuthPolicyDb.PubdidGroups> privatePubdidGroups = adb.queryGroupsPrivateOnly(uniqPubdids);
+         return privatePubdidGroups;
+      }
+   }
+
+
+
+   private List<String> pubdidsNotAuthorized(List<AuthPolicyDb.PubdidGroups> pubdidList, String[] userGroups)
+   {
+      LOGGER.info("trace");
+      //LOGGER.info("userGroups: " + String.join(" ",userGroups));
+
+      List<String> pubdidsNotAuthorizedList = new LinkedList<String>();
+      ListIterator<AuthPolicyDb.PubdidGroups> it = pubdidList.listIterator();
+
+      while (it.hasNext())
+      {
+         AuthPolicyDb.PubdidGroups pubdidGroups = it.next();
+
+         //LOGGER.info(pubdidGroups.pubdid + " : " + String.join(" ",pubdidGroups.groups));
+
+         if( isIntersectionEmpty(pubdidGroups.groups, userGroups) )
+         {
+            pubdidsNotAuthorizedList.add(pubdidGroups.pubdid);
+         }
+      }
+
+      return pubdidsNotAuthorizedList;
+   }
+
+
+
+   private boolean isIntersectionEmpty(String[] stringsA, String[] stringsB)
+   {
+      for(String strA : stringsA)
+         for(String strB : stringsB)
+         {
+            if(strA.equals(strB))
+            {
+               return false;
+            }
+         }
+      return true;
+   }
+
+
+
+}
+
diff --git a/auth/src/main/java/AuthPolicyDb.java b/auth/src/main/java/AuthPolicyDb.java
new file mode 100644
index 0000000000000000000000000000000000000000..9f737ece7415810ae6d92fecca867f70d7385675
--- /dev/null
+++ b/auth/src/main/java/AuthPolicyDb.java
@@ -0,0 +1,256 @@
+
+import java.util.logging.Logger;
+
+// mySQL access
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.Driver;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.sql.SQLException;
+import java.sql.Array;
+// import javax.sql.*; needed if using DataSource instead of DriverManager for DB-connections
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import java.util.Enumeration;
+import java.util.List;
+import java.util.LinkedList;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.ArrayList;
+
+import java.lang.ClassNotFoundException;
+
+
+
+public class AuthPolicyDb
+{
+   private static final Logger LOGGER = Logger.getLogger(AuthPolicyDb.class.getName());
+
+   private static final String DB_DRIVER = "org.postgresql.Driver";
+   //private static final Settings settings = Settings.getInstance();
+   //static public Settings.DBConn dbconn = settings.dbConn;
+   static public String dbConnUrl;
+   static public String dbUserName;
+   static public String dbPassword;
+
+   private Connection conn;
+   private Statement  st;
+   private ResultSet  res;
+
+   AuthPolicyDb(){
+      conn = null;
+      st   = null;
+      res  = null;
+   }
+
+
+
+   public class PubdidGroups
+   {
+      String pubdid;
+      String[] groups;
+      PubdidGroups(String pubdid, String[] groups)
+      {
+         this.pubdid = pubdid;
+         this.groups = groups;
+      }
+   }
+
+
+/*
+   private String convertToVlkbPubdid(String obscorePubdid)
+   {
+      final String PUBDID_PREFIX = dbconn.obscorePublisher;
+
+      if(obscorePubdid.startsWith(PUBDID_PREFIX))
+         return obscorePubdid.substring( PUBDID_PREFIX.length() );
+      else
+         return obscorePubdid;
+   }
+
+   private Set<String> convertToObscorePubdids(Set<String> vlkbPubdids)
+   {
+      final String PUBDID_PREFIX = dbconn.obscorePublisher;
+
+      Set<String> obscorePubdids = new HashSet<String>();
+
+      for(String pubdid : vlkbPubdids)
+      {
+         String obscorePubdid =  "\'" + PUBDID_PREFIX + pubdid + "\'";
+         obscorePubdids.add(obscorePubdid);
+      }
+
+      return obscorePubdids;
+   }
+*/
+
+   public List<PubdidGroups> queryGroupsPrivateOnly(Set<String> uniqPubdids)
+   {
+      //Set<String> uniqObscorePubdids = convertToObscorePubdids(uniqPubdids);
+      Set<String> uniqObscorePubdids = uniqPubdids;
+      String commaSepObscorePubdids  = String.join("\',\'", uniqObscorePubdids);
+
+      assert (commaSepObscorePubdids != null) && (!commaSepObscorePubdids.isEmpty());
+
+      String TheQuery = "SELECT obs_publisher_did,groups FROM obscore "
+         + "WHERE (policy = 'PRIV') AND (obs_publisher_did IN (\'"+commaSepObscorePubdids+"\'));";
+
+      // FIXME use separate table holding  _only_  private data-id's
+      //String TheQuery = "SELECT obs_publisher_did,groups FROM permissions "
+      //   + "WHERE (obs_publisher_did IN (\'"+commaSepObscorePubdids+"\'));";
+
+      //LOGGER.info(TheQuery);
+
+      List<PubdidGroups> pubdidGroups = new LinkedList<PubdidGroups>();
+      try
+      {
+         res = doQuery(TheQuery);
+
+         while (res.next())
+         {
+            //String pubdid   = convertToVlkbPubdid(res.getString("obs_publisher_did"));
+            String pubdid   = res.getString("obs_publisher_did");
+            Array groupsArr = res.getArray("groups");
+
+            String[] groups   = null;
+            if(groupsArr == null)
+               groups = null;
+            else
+               groups = (String[]) groupsArr.getArray();
+
+            PubdidGroups pg = new PubdidGroups(pubdid, groups);
+            pubdidGroups.add(pg); 
+         }
+      }
+      catch (SQLException se)
+      {
+         logSqlExInfo(se);
+         se.printStackTrace();
+      }
+      catch (ClassNotFoundException e)
+      {
+         LOGGER.info("DB driver "+ DB_DRIVER +" not found: " + e.getMessage());
+         e.printStackTrace();
+      }
+      finally
+      {
+         closeAll();
+      }
+
+      return pubdidGroups; 
+   }
+
+
+   private void closeAll()
+   {
+         if(res  != null ) try { res.close(); } catch(Exception e) {LOGGER.info("DB ResultSet::close() failed");}
+         if(st   != null ) try { st.close();  } catch(Exception e) {LOGGER.info("DB Statement::close() failed");}
+         if(conn != null ) try { conn.close();} catch(Exception e) {LOGGER.info("DB Connection::close() failed");} 
+  }
+
+   private void logSqlExInfo(SQLException se){
+
+      /* dbconn.print_class_vars(); */
+
+      System.err.println("SQLState : " + se.getSQLState());
+      System.err.println("ErrorCode: " + se.getErrorCode());
+      System.err.println("Message  : " + se.getMessage());
+      Throwable t = se.getCause();
+      while(t != null) {
+         System.err.println("Cause: " + t);
+         t = t.getCause();
+      }
+   }
+
+
+
+   private ResultSet doQuery(String TheQuery)
+      throws SQLException, ClassNotFoundException 
+   {
+
+      /* https://docs.oracle.com/javase/tutorial/jdbc/basics/connecting.html :
+         Any JDBC 4.0 drivers that are found in your class path are automatically loaded.
+         (However, you must manually load any drivers prior to JDBC 4.0 with the method
+         Class.forName.)
+         */
+      // try {
+//      Class.forName(DB_DRIVER);
+      /* OR
+         DriverManager.registerDriver(new org.postgresql.Driver());
+         */
+
+      /*LOGGER.info(getClasspathString());*/
+      LOGGER.info(getRegisteredDriverList());
+
+      // FIXME seems DriverManager expects jdbc:postgresql driver scheme, it does not support postgresql:// scheme
+      // additionally:
+      // jdbc:postgresql:// scheme does not support username:password in the URL. 
+      // So:
+      // receive postgresql:// scheme with user:password and convert to jdbc:postgresql://
+      // by extracting userName and password from the URL-string and prepending 'jdbc:'
+      // 
+
+      /*         LOGGER.info("DBMS URL: " + dbConnUrl);
+                 URI dbConnUri = new URI(dbConnUrl);
+
+                 String userInfoString = dbConnUri.getUserInfo(); 
+
+                 if(userInfoString == null) throw new AssertionError("DBMS URL must contain user:password but it is: " + dbConnUrl);
+
+                 String[] userInfo = userInfoString.split(":"); 
+
+                 if(userInfo.length < 2) throw new AssertionError("DBMS URL must contain user:password but it is: " + dbConnUrl);
+
+                 String userName = userInfo[0];
+                 String password = userInfo[1];
+
+                 String dbConnJdbcUrl = "jdbc:" + dbConnUrl.replace(userInfoString + "@", "");
+                 */       LOGGER.info("DBMS URL: " + dbConnUrl);
+      LOGGER.info("DBMS userName: " + dbUserName);
+      LOGGER.info("DBMS password: " + dbPassword);
+
+      conn = DriverManager.getConnection(dbConnUrl, dbUserName, dbPassword);
+
+      st = conn.createStatement();
+
+      // } catch (Exception e){ e.printStackTrace();}
+
+      return st.executeQuery(TheQuery);
+   }
+
+
+   private String getClasspathString() {
+      StringBuffer classpath = new StringBuffer("getClasspathString:\r\n");
+      ClassLoader applicationClassLoader = this.getClass().getClassLoader();
+      if (applicationClassLoader == null) {
+         applicationClassLoader = ClassLoader.getSystemClassLoader();
+      }
+      URL[] urls = ((URLClassLoader)applicationClassLoader).getURLs();
+      for(int i=0; i < urls.length; i++) {
+         classpath.append(urls[i].getFile()).append("\r\n");
+      }
+
+      return classpath.toString();
+   }
+
+
+   private String getRegisteredDriverList()
+   {
+      StringBuffer drvList = new StringBuffer("getRegisteredDriverList:\r\n");
+      for (Enumeration e = DriverManager.getDrivers();
+            e.hasMoreElements(); )
+      {
+         Driver d = (Driver) e.nextElement();
+         String driverClass = d.getClass().getName();
+         drvList.append(driverClass).append("\r\n");	
+      }
+      return drvList.toString();
+   }
+
+
+}
diff --git a/auth/src/main/java/IA2TokenConvFilter.java b/auth/src/main/java/IA2TokenConvFilter.java
new file mode 100644
index 0000000000000000000000000000000000000000..a64c181ed275cda3bfd0af2e7b84a2b37dc0f709
--- /dev/null
+++ b/auth/src/main/java/IA2TokenConvFilter.java
@@ -0,0 +1,130 @@
+
+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 IA2TokenConvFilter implements Filter
+{
+  private static final Logger LOGGER = Logger.getLogger("IA2TokenConvFilter");
+
+   @Override
+   public void init(FilterConfig fc) throws ServletException
+   {
+      LOGGER.info("trace");
+   }
+
+   @Override
+   public void destroy()
+   {
+      LOGGER.info("trace");
+   }
+
+   @Override
+   public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
+                   throws IOException, ServletException
+   {
+      LOGGER.info("trace");
+
+        HttpServletRequest  request  = (HttpServletRequest)  req;
+        HttpServletResponse response = (HttpServletResponse) res;
+
+        String authHeader = request.getHeader("Authorization");
+        if (authHeader != null)
+        {
+            LOGGER.info("Authorization header: " + authHeader.substring(0, 7+60) + " ...");
+            if (authHeader.startsWith("Bearer "))
+            {
+
+
+               Principal principal = request.getUserPrincipal();
+               if(principal == null)
+               {
+                   LOGGER.warning("User principal is null");
+                   response.sendError(500, "Internal error - User principal is not correct");
+                   return;
+               }
+
+               VlkbUser user = new VlkbUser();
+
+               if(principal instanceof it.inaf.ia2.aa.data.User)
+               {
+                  it.inaf.ia2.aa.data.User alUser = (it.inaf.ia2.aa.data.User) principal;
+
+                  String userId       = alUser.getName();//UserId
+                  String userLabel    = alUser.getUserLabel();
+                  List<String> groups = alUser.getGroups();
+
+                  // FIXME check is any NULL ?
+
+                  user.setUserId(userId);
+                  user.setUserLabel(userLabel);
+                  user.setGroups(groups);
+               }
+               else
+               {
+                   LOGGER.warning("User principal is incorrect type");
+                   response.sendError(500, "Internal error - User principal is not correct type");
+                   return;
+               }
+
+               HttpServletRequestWrapper requestWithPrincipal
+                             = new RequestWithPrincipal(request, user);
+
+               chain.doFilter(requestWithPrincipal, response);
+               return;
+            }
+            else
+            {
+                LOGGER.warning("Detected Authorization header without Bearer token.");
+            }
+        }
+        else
+        {
+            LOGGER.warning("Request has no Authorization header.");
+            if(request.getUserPrincipal() != null)
+            {
+                   LOGGER.warning("User principal is set however no Authorization header present");
+            //       response.sendError(500, "Internal error - It is not expected that Principal set in request but there is no Auhtprozation in HTTP-Header"); // FIXME use other err code not 500 here
+             //      return;
+            }
+        }
+        chain.doFilter(request, response);
+//         response.sendError(401, "Unauthorized");
+   }
+
+
+
+   private static class RequestWithPrincipal extends HttpServletRequestWrapper
+   {
+      private final VlkbUser user;
+
+         public RequestWithPrincipal(HttpServletRequest request, VlkbUser user)
+               {   
+                        super(request);
+                              this.user = user;
+                                 }   
+
+            @Override
+               public Principal getUserPrincipal() {
+                        return user;
+                           }   
+   }
+
+}
+
diff --git a/auth/src/main/java/IamSigningKeyResolver.java b/auth/src/main/java/IamSigningKeyResolver.java
new file mode 100644
index 0000000000000000000000000000000000000000..ff89893067b2748ea91f6cda0d638eb0b3ea9126
--- /dev/null
+++ b/auth/src/main/java/IamSigningKeyResolver.java
@@ -0,0 +1,153 @@
+
+// 1. HTTPS
+import java.net.URL;
+import java.io.*;
+import javax.net.ssl.HttpsURLConnection;
+
+// 2. json deser
+//import org.codehaus.jackson.map.ObjectMapper;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+
+
+// 3, extract PublicKey
+import java.util.Base64;
+import java.io.ByteArrayInputStream;
+import java.security.GeneralSecurityException; 
+import java.security.PublicKey; 
+import java.security.Signature; 
+import java.security.cert.CertificateFactory; 
+import java.security.cert.X509Certificate; 
+
+// 4, validate token
+import java.security.spec.InvalidKeySpecException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Key;
+import java.security.PublicKey;
+import java.security.interfaces.RSAPublicKey;
+import io.jsonwebtoken.Header;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwt;
+import io.jsonwebtoken.Jws;
+import io.jsonwebtoken.JwsHeader;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.jackson.io.JacksonDeserializer;
+import io.jsonwebtoken.SigningKeyResolverAdapter;
+import io.jsonwebtoken.security.Jwk;
+import io.jsonwebtoken.security.Jwks;
+// only dbg: when keys taken from file, not URL
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import java.util.logging.Logger;
+
+public class IamSigningKeyResolver extends SigningKeyResolverAdapter
+{
+   private static final Logger LOGGER = Logger.getLogger(IamSigningKeyResolver.class.getName());
+   private String keysURL;
+
+
+   public IamSigningKeyResolver(String keysUrl) {this.keysURL = keysUrl;}
+
+   @Override
+   public Key resolveSigningKey(JwsHeader jwsHeader, Claims claims)
+   {
+      LOGGER.info( "IamSigningKeyResolver::resolveSigningKey" );
+
+      //inspect the header or claims, lookup and return the signing key
+
+      String keyId = jwsHeader.getKeyId(); //or any other field that you need to inspect
+
+      Key key = null;
+      try
+      {
+         key = lookupVerificationKey(keyId); //implement me
+      }
+      catch(Exception e)
+      {
+         e.printStackTrace();
+      }
+
+      return key;
+   }
+
+
+
+   private Key lookupVerificationKey(String keyId)
+         throws Exception, GeneralSecurityException
+      {
+         LOGGER.info( "IamSigningKeyResolver::lookupVerificationKey" );
+
+         String jsonKeys = doHttps();
+
+         PublicKey pubKey = (PublicKey)getKeyFromKeys(jsonKeys, keyId);
+
+         return pubKey;
+      }
+
+
+   private String doHttps() throws Exception
+   {
+      LOGGER.info("doHttps : " + keysURL);
+
+      URL myUrl = new URL(keysURL);
+      HttpsURLConnection conn = (HttpsURLConnection)myUrl.openConnection();
+      InputStream is = conn.getInputStream();
+      InputStreamReader isr = new InputStreamReader(is);
+      BufferedReader br = new BufferedReader(isr);
+
+      String inputLine;
+      String jsonKeys = ""; 
+      while ((inputLine = br.readLine()) != null) {
+         jsonKeys = jsonKeys + inputLine;
+      }
+
+      br.close();
+
+      return jsonKeys;
+   }
+
+
+   private Key getKeyFromKeys(String jsonKeys, String keyId)
+         throws JsonProcessingException, GeneralSecurityException, IOException
+      {
+         LOGGER.info( "IamSigningKeyResolver::getKeyFromKeys");
+
+         Key key = null;
+
+         ObjectMapper mapper = new ObjectMapper();
+
+         JsonNode keysNode = mapper.readTree(jsonKeys).get("keys");
+         if(keysNode.isArray())
+         {
+            for (JsonNode node : keysNode)
+            {
+               String nodeContent = mapper.writeValueAsString(node);
+
+               LOGGER.info("key: " + nodeContent);
+
+               Jwk<?> jwk = Jwks.parser().build().parse(nodeContent);
+
+               String jwkkid = jwk.getId();
+
+               LOGGER.info("kid-token : " + keyId + "kid-store : " + jwkkid + " key-type: " + jwk.getType());
+
+               if(keyId.equals(jwkkid))
+               {
+                  key = jwk.toKey();
+               }
+            }
+         }
+
+         return key;
+      }
+
+}
+
diff --git a/auth/src/main/java/IamTokenFilter.java b/auth/src/main/java/IamTokenFilter.java
new file mode 100644
index 0000000000000000000000000000000000000000..f434f824fb0f3e03e69398bd23795d69f148d5eb
--- /dev/null
+++ b/auth/src/main/java/IamTokenFilter.java
@@ -0,0 +1,431 @@
+
+import io.jsonwebtoken.JwtException;
+import io.jsonwebtoken.InvalidClaimException;
+import io.jsonwebtoken.Header;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwt;
+import io.jsonwebtoken.Jws;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.jackson.io.JacksonDeserializer;
+
+import java.security.spec.InvalidKeySpecException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.servlet.http.HttpServletRequestWrapper;
+import java.security.Principal;
+
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.IOException;
+import java.util.List; // ArrayList<String>
+import java.util.Map;
+import java.util.HashMap;
+import java.util.*;
+
+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.ServletOutputStream;
+
+public class IamTokenFilter implements Filter
+{
+   private static final Logger LOGGER = Logger.getLogger("IamTokenFilter");
+   private static final IamTokenSettings settings = IamTokenSettings.getInstance();
+
+   final String RESPONSE_ENCODING = "utf-8";
+
+   final String keysUrl = settings.security.jwksEndpoint;
+   final String INTROSPECT_URL = settings.getIntrospectUrl();
+   final String CLIENT_PASS = settings.getClientName() + ":" + settings.getClientPassword();
+
+
+   @Override
+   public void init(FilterConfig fc) throws ServletException {}
+
+   @Override
+   public void destroy() {}
+
+
+   @Override
+   public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
+      throws IOException, ServletException
+   {
+      String authHeader = ((HttpServletRequest)req).getHeader("Authorization");
+
+      ServletOutputStream  respOutputStream = resp.getOutputStream();
+      PrintWriter writer = new PrintWriter(new OutputStreamWriter(respOutputStream, RESPONSE_ENCODING));
+
+      if(authHeader==null)
+      {
+         final String AUTH_ERR = "Request without Authorization header. Only authenticated requests allowed.";
+         LOGGER.info(AUTH_ERR);
+         sendAuthenticationError((HttpServletResponse)resp, writer, AUTH_ERR);
+      }
+      else
+      {
+         authHeader = authHeader.trim();
+
+         if (authHeader.startsWith("Bearer ") && (authHeader.length() > "Bearer ".length()))
+         {
+            LOGGER.info("Request with Authorization header and has Bearer entry");
+            String token = authHeader.substring("Bearer ".length()).trim();
+
+            doFilterBearer(req, token, resp, chain);
+         }
+         else
+         {
+            final String AUTH_ERR = "Authorization header with Bearer-token expected, but it starts with : "
+               + authHeader.substring(0, "Bearer ".length()) + "...";
+            LOGGER.info(AUTH_ERR);
+            sendUsageError((HttpServletResponse)resp, writer, AUTH_ERR);
+         }
+      }
+   }
+
+
+
+
+   private void doFilterBearer(ServletRequest req, String token, ServletResponse resp, FilterChain chain)
+         throws IOException, ServletException
+      {
+         HttpServletRequest  request  = (HttpServletRequest) req;
+         HttpServletResponse response = (HttpServletResponse)resp;
+
+         ServletOutputStream  respOutputStream = response.getOutputStream();
+         PrintWriter writer = new PrintWriter(new OutputStreamWriter(respOutputStream, RESPONSE_ENCODING));
+
+         try
+         {
+            IntrospectResponse insResp = new IntrospectResponse(CLIENT_PASS, INTROSPECT_URL, token);
+
+            if(insResp.isTokenActive())
+            {
+               String idString = request.getParameter("ID");
+               Ivoid ivoid = new Ivoid(idString);
+
+               String ivoidPath = ivoid.getLocalPart();
+               String tokenPath = insResp.getPathFromStorageReadScope();
+
+               LOGGER.info("Path from IVOID: " + ivoidPath);
+               LOGGER.info("Path from token: " + tokenPath);
+
+               if(tokenPath.endsWith(ivoidPath))
+               {
+                  LOGGER.info("Access authorized.");
+                  chain.doFilter(request, response);
+               }
+               else
+               {
+                  final String AUTH_ERR = "Bearer token does not authorize access to : " + ivoidPath;
+                  LOGGER.info(AUTH_ERR);
+                  sendAuthorizationError(response, writer, AUTH_ERR);
+               }
+            }
+            else
+            {
+               final String AUTH_ERR = "Bearer-token is inactive.";
+               LOGGER.info(AUTH_ERR);
+               sendAuthorizationError(response, writer, AUTH_ERR);
+            }
+
+         }
+         catch(IndexOutOfBoundsException ex)
+         {
+            LOGGER.info("IndexOutOfBoundsException: " + ex.getMessage());
+            sendUsageError(response, writer, ex.getMessage());
+         }
+         catch(IllegalArgumentException ex)
+         {
+            LOGGER.info("IllegalArgumentException: " + ex.getMessage());
+            sendUsageError(response, writer, ex.getMessage());
+         }
+         catch(Exception ex)
+         {
+            LOGGER.info("Exception: " + ex.getMessage());
+            ex.printStackTrace();
+            sendError(response, writer, ex.toString());
+         }
+         finally
+         {
+            writer.close();
+            respOutputStream.close();
+         }
+      }
+
+
+
+   // 5. SODA sunc Responses [Table 6]
+   // Success: 200 (Ok) or 204 (NO Content) and set HTTP-Headers: Content-Type & Content_encoding (if applicable)
+   //Error Code          Description
+   //===================================================================================
+   //Error               General error (not covered below)
+   //AuthenticationError Not authenticated
+   //AuthorizationError  Not authorized to access the resource
+   //ServiceUnavailable  Transient error (could succeed with retry)
+   //UsageError          Permanent error (retry pointless)
+   //MultiValuedParamNotSupported  request included multiple values for a parameter
+   //                              but the service only supports a single value 
+
+
+   protected void sendError(HttpServletResponse response, PrintWriter printWriter, String message)
+   {
+      response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+      response.setContentType("text/plain");
+      printWriter.println("Error : " + message);
+   }
+
+
+   protected void sendAuthenticationError(HttpServletResponse response, PrintWriter printWriter, String message)
+   {
+      response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+      response.setContentType("text/plain");
+      printWriter.println("AuthenticationError : " + message);
+   }
+
+
+   protected void sendAuthorizationError(HttpServletResponse response, PrintWriter printWriter, String message)
+   {
+      response.setStatus(HttpServletResponse.SC_FORBIDDEN);
+      response.setContentType("text/plain");
+      printWriter.println("AuthorizationError : " + message);
+   }
+
+
+   protected void sendServiceUnavailable(HttpServletResponse response, PrintWriter printWriter, String message)
+   {
+      response.setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
+      response.setContentType("text/plain");
+      printWriter.println("ServiceUnavailable : " + message);
+   }
+
+
+   protected void sendUsageError(HttpServletResponse response, PrintWriter printWriter, String message)
+   {
+      response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+      response.setContentType("text/plain");
+      printWriter.println("UsageError : " + message);
+   }
+
+
+   protected void sendMultiValuedParamNotSupported(HttpServletResponse response, PrintWriter printWriter, String message)
+   {
+      response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+      response.setContentType("text/plain");
+      printWriter.println("MultiValuedParamNotSupported : " + message);
+   }
+
+
+
+
+
+
+   // Implementation with JWKs endpoint (explicit signiture verification):
+
+   //final String resourceId = settings.security.resourceId; //"vlkb"
+   //final String realmName = "neanias-production";
+   //final String keysUrl = "https://sso.neanias.eu/auth/realms/" + realmName + "/protocol/openid-connect/certs";
+
+   /*/@Override
+     public void OLD_doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
+        throws IOException, ServletException
+     {
+     HttpServletRequest request = (HttpServletRequest) req;
+     HttpServletResponse response = (HttpServletResponse) res;
+
+     String  qString = request.getQueryString();
+     if(qString == null)
+     LOGGER.info(request.getRequestURL().toString());
+     else
+     LOGGER.info(request.getRequestURL() + "    " + qString);
+
+     String authHeader = request.getHeader("Authorization");
+     if (authHeader == null)
+     {
+     boolean non_authenticated_request = (settings.security.non_authn_username != null);
+
+     if(non_authenticated_request)
+     {
+     chain.doFilter(request, response);
+     }
+     else
+     {
+     LOGGER.info("Request without Authorization header, no Principal added");
+     response.sendError(HttpServletResponse.SC_BAD_REQUEST,
+     "No Authorization in HTTP-header. Only authorized requests allowed.");
+     }
+     return;
+     }
+     else
+     {
+
+     if (authHeader.startsWith("Bearer ") && (authHeader.length() > "Bearer ".length()))
+     {
+     LOGGER.info("Request with Authorization header and has Bearer entry");
+
+     String jws = authHeader.substring("Bearer ".length());
+
+     try
+     {
+     VlkbUser user = getUserFromAccessToken(jws);
+
+     HttpServletRequestWrapper requestWithPrincipal
+     = new RequestWithPrincipal(request, user);
+
+     chain.doFilter(requestWithPrincipal, response);
+     return;
+
+     }
+     catch (JwtException | InvalidTokenException ex)
+     {
+     LOGGER.warning("Token invalid: " + ex.toString());
+     response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Token invalid");
+     return;
+     }
+     catch (Exception ex)
+     {
+     LOGGER.severe(ex.toString());
+     response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+     "Error during authorization");
+     return;
+     }
+
+     }
+     else
+     {
+     LOGGER.warning("Request with Authorization header but without Bearer token");
+
+     response.sendError(HttpServletResponse.SC_BAD_REQUEST,
+     "Only Bearer authorization supported or token missing");
+   return;
+     }
+
+     }
+     }
+*/
+
+
+   /*/ validate and parse the token
+
+     private List<String> parseScopes(Claims claims)
+     {
+     String scopeStr = (String)claims.get("scope");
+     List<String> scopes = new ArrayList<String>(Arrays.asList(scopeStr.split(" ")));
+
+     if (scopes.stream().anyMatch(s -> s.startsWith("storage.read:")))
+     {
+     return scopes;
+     }
+     else
+     {
+     final String AUTH_ERR = "Invalid token: missing storage.read scope: " + scopeStr;
+     LOGGER.warning(AUTH_ERR);
+     throw new InvalidTokenException(AUTH_ERR);
+     }
+     }
+
+
+
+     VlkbUser getUserFromAccessToken(String jwsString)
+   //throws JwtException, InvalidTokenException  <-- FIXME
+   {
+   long clockSkew = 3 * 60; //3 minutes FIXME get from Config file
+
+   Jws<Claims> jws = Jwts
+   .parser()
+   //.setAllowedClockSkewSeconds(clockSkew) // FIXME needed ?
+   .setSigningKeyResolver(new IamSigningKeyResolver(keysUrl))
+   .build()
+   .parseClaimsJws(jwsString);
+
+   Claims claims = jws.getBody();
+
+   LOGGER.info("scope: " + (String)claims.get("scope"));
+
+   List<String> scopes = parseScopes(claims);
+
+   String storageReadScope = "";
+
+   for(int i=0;i<scopes.size();i++)
+   {
+   if(scopes.get(i).startsWith("storage.read:"))
+   {
+   storageReadScope = scopes.get(i);
+   }
+   }
+
+   LOGGER.info("storage.read: " + storageReadScope);
+
+   String path = storageReadScope.substring(storageReadScope.lastIndexOf(":") + 1);
+
+   LOGGER.info("path: " + path);
+
+   // set User/Principal
+
+   VlkbUser user = new VlkbUser();
+   user.setAccessToken(jwsString);
+   user.setExpirationTime(0);//FIXME
+   user.setUserId((String) claims.get("sub"));
+   user.setUserLabel((String) claims.get("name"));
+   user.setGroups(scopes); // FIXME temp store scopes where roles were in neanias
+
+   return user;
+   }
+
+
+
+
+
+   private static class RequestWithPrincipal extends HttpServletRequestWrapper
+   {
+      private final VlkbUser user;
+
+      public RequestWithPrincipal(HttpServletRequest request, VlkbUser user)
+      {
+         super(request);
+         this.user = user;
+      }
+
+      @Override
+      public Principal getUserPrincipal() {
+         return user;
+      }
+   }
+*/
+
+
+
+
+   /*
+      private boolean isMapStringObject(Object obj)
+      {
+      if(obj instanceof Map)
+      {
+      Map map = (Map)obj;
+
+      Set<?> s = map.keySet();
+      Iterator<?> it = s.iterator();
+      while(it.hasNext())
+      {
+      Object el = it.next();
+      if(! (el instanceof String))
+      {
+      return false;
+      }
+      }
+
+      return true;
+      }
+      else
+      return false;
+      }
+      */
+
+
+}
+
diff --git a/auth/src/main/java/IamTokenSettings.java b/auth/src/main/java/IamTokenSettings.java
new file mode 100644
index 0000000000000000000000000000000000000000..a5ec5a2d3f2f5c8084851bdc90153f92eda83aef
--- /dev/null
+++ b/auth/src/main/java/IamTokenSettings.java
@@ -0,0 +1,106 @@
+
+import java.util.logging.Logger;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+import java.io.PrintWriter;
+
+/* for Csv-loadSubsurveys * /
+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;
+*/
+
+
+
+class IamTokenSettings
+{
+   private static final Logger LOGGER = Logger.getLogger("IamTokenSettings");
+
+   static final String VLKB_PROPERTIES = "iamtoken.properties";
+
+   public static class Security
+   {
+      String jwksEndpoint;
+      String introspectEndpoint;
+      String clientName;
+      String clientPassword;
+      String resourceId;
+
+      String non_authn_username = null;
+   }
+
+   public Security security;
+
+
+   // will not start without config-file
+   // no reasonable code-defaults can be invented
+   public static IamTokenSettings getInstance()
+   {
+      try
+      {
+         InputStream ins =
+            IamTokenSettings.class.getClassLoader().getResourceAsStream(VLKB_PROPERTIES);
+
+         if (ins != null)
+         {
+            Properties properties = new Properties();
+            properties.load(ins);
+
+            Security  security = loadSecurity(properties);
+
+            return new IamTokenSettings(security);
+         }
+         else
+         {
+            throw new IllegalStateException(VLKB_PROPERTIES + " not found in classpath");
+         }
+
+      }
+      catch(IOException ex)
+      {
+         throw new IllegalStateException("Error while loading " + VLKB_PROPERTIES + " file", ex);
+      }
+   }
+
+   public String getIntrospectUrl()  { return this.security.introspectEndpoint; }
+   public String getClientName()     { return this.security.clientName; }
+   public String getClientPassword() { return this.security.clientPassword; }
+
+/* FIXME all  fail if reurned string is null  */
+
+
+   private IamTokenSettings(Security security)
+   {
+      this.security  = security;
+   }
+
+
+   private static Security loadSecurity(Properties properties)
+   {
+      Security security = new IamTokenSettings.Security();
+      security.jwksEndpoint = getPropertyStriped(properties, "jwks_url");
+      security.introspectEndpoint = getPropertyStriped(properties, "introspect");
+      security.clientName = getPropertyStriped(properties, "client_name");
+      security.clientPassword = getPropertyStriped(properties, "client_password");
+      security.resourceId = getPropertyStriped(properties, "resource_id");
+      security.non_authn_username = getPropertyStriped(properties, "non_authenticated_username");
+      return security;
+   }
+
+
+   private static String getPropertyStriped(Properties properties, String setting)
+   {
+      String st = properties.getProperty(setting);
+      if(st != null) return st.strip();
+      else return st;
+   }
+
+
+}
+
diff --git a/auth/src/main/java/IntrospectResponse.java b/auth/src/main/java/IntrospectResponse.java
new file mode 100644
index 0000000000000000000000000000000000000000..4ac4b8b0e519cf051c34e6f47683a944bcfd1174
--- /dev/null
+++ b/auth/src/main/java/IntrospectResponse.java
@@ -0,0 +1,111 @@
+
+import java.util.logging.Logger;
+
+// 1. Https
+import java.net.URL;
+import java.io.*;
+import javax.net.ssl.HttpsURLConnection;
+
+// 2. json deser
+//import org.codehaus.jackson.map.ObjectMapper;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+
+class IntrospectResponse
+{
+   private static final Logger LOGGER = Logger.getLogger("IntrospectResponse");
+
+   public boolean active;
+   public String  scope;
+
+   public IntrospectResponse(String uPass, String url, String token) throws Exception
+   {
+      final String POST_PARAM = "token=" + token;
+      String resp = doHttps(uPass, url, POST_PARAM);
+      decodeIRespJson(resp);
+   }
+
+   public boolean isTokenActive() { return active; }
+
+   public String getPathFromStorageReadScope()
+   {
+      if(scope == null)
+      {
+         throw new IllegalStateException("Introspect Response has scope = null. Probably token not active.");
+      }
+      else
+      {
+         String[] scopes = scope.split(" ");
+         for(String scope : scopes)
+         {
+            if(scope.startsWith("storage.read:"))
+            {
+               return scope.substring("storage.read:".length());
+            }
+         }
+         throw new IllegalStateException(
+               "Introspect Response with 'storage.read' scope expected, but received scope: " + scope);
+      }
+   }
+
+
+
+   private String doHttps(String uPass, String url, String postParams) throws Exception
+   {
+      LOGGER.info("doHttps : " + url);
+
+      URL myUrl = new URL(url);
+      HttpsURLConnection conn = (HttpsURLConnection)myUrl.openConnection();
+      conn.setRequestMethod("POST");
+
+      String basicAuth = "Basic " + javax.xml.bind.DatatypeConverter.printBase64Binary(uPass.getBytes());
+
+      conn.setRequestProperty ("Authorization", basicAuth);
+
+
+      conn.setDoOutput(true);
+      OutputStream os = conn.getOutputStream();
+      os.write(postParams.getBytes());
+      os.flush();
+      os.close();
+
+      InputStream is = conn.getInputStream();
+      InputStreamReader isr = new InputStreamReader(is);
+      BufferedReader br = new BufferedReader(isr);
+
+      String inputLine;
+      String jsonKeys = "";
+      while ((inputLine = br.readLine()) != null) {
+         jsonKeys = jsonKeys + inputLine;
+      }
+
+      br.close();
+
+      return jsonKeys;
+   }
+   /*
+      @JsonAutoDetect(fieldVisibility=JsonAutoDetect.Visibility.ANY);
+      static class IResp
+      {
+      boolean active;
+      String[] scope;
+      }
+      */
+   private void decodeIRespJson(String json) throws IOException
+   {
+      ObjectMapper mapper = new ObjectMapper();
+      active = mapper.readTree(json).get("active").asBoolean();
+      if(active)
+      {
+         scope = mapper.readTree(json).get("scope").asText();
+      }
+   }
+
+}
diff --git a/auth/src/main/java/InvalidTokenException.java b/auth/src/main/java/InvalidTokenException.java
new file mode 100644
index 0000000000000000000000000000000000000000..c248c9ffed6eea80fb76469218f5cbce55edb057
--- /dev/null
+++ b/auth/src/main/java/InvalidTokenException.java
@@ -0,0 +1,10 @@
+
+
+public class InvalidTokenException extends RuntimeException
+{
+   public InvalidTokenException(String message)
+   {
+      super(message);
+   }
+}
+
diff --git a/auth/src/main/java/Ivoid.java b/auth/src/main/java/Ivoid.java
new file mode 100644
index 0000000000000000000000000000000000000000..5b36ade61214ebecd4295e60411d715b91257109
--- /dev/null
+++ b/auth/src/main/java/Ivoid.java
@@ -0,0 +1,30 @@
+
+
+class Ivoid
+{
+   private String localPart;
+
+   public Ivoid(String ivoid)
+   {
+      if(ivoid.startsWith("ivo://"))
+      {
+         int lastQ = ivoid.lastIndexOf("?");
+         if( lastQ < 0 )
+         {
+            throw new IllegalArgumentException("IVOID must contain '?' but none found in: " + ivoid);
+         }
+         else
+         {
+            localPart = ivoid.substring(lastQ + 1);// +1: skip '?'
+            // throws IndexOutOfBoundsException
+            // if lastQ = str.length -> returns "" (empty string)
+         }
+      }
+      else
+      {
+         throw new IllegalArgumentException("IVOID must start with 'ivo://' but it is: " + ivoid);
+      }
+   }
+
+   public String getLocalPart(){ return localPart; }
+}
diff --git a/auth/src/main/java/NeaSigningKeyResolver.java b/auth/src/main/java/NeaSigningKeyResolver.java
new file mode 100644
index 0000000000000000000000000000000000000000..72af2343a183b24d4c8e9977fd0823ad4fb05200
--- /dev/null
+++ b/auth/src/main/java/NeaSigningKeyResolver.java
@@ -0,0 +1,191 @@
+
+// 1. HTTPS
+import java.net.URL;
+import java.io.*;
+import javax.net.ssl.HttpsURLConnection;
+
+// 2. json deser
+//import org.codehaus.jackson.map.ObjectMapper;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+
+
+// 3, extract PublicKey
+import java.util.Base64;
+import java.io.ByteArrayInputStream;
+import java.security.GeneralSecurityException; 
+import java.security.PublicKey; 
+import java.security.Signature; 
+import java.security.cert.CertificateFactory; 
+import java.security.cert.X509Certificate; 
+
+// 4, validate token
+import java.security.spec.InvalidKeySpecException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Key;
+import java.security.PublicKey;
+import io.jsonwebtoken.Header;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwt;
+import io.jsonwebtoken.Jws;
+import io.jsonwebtoken.JwsHeader;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.jackson.io.JacksonDeserializer;
+import io.jsonwebtoken.SigningKeyResolverAdapter;
+
+// only dbg: when keys taken from file, not URL
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+import java.util.logging.Logger;
+
+public class NeaSigningKeyResolver extends SigningKeyResolverAdapter
+{
+   private static final Logger LOGGER = Logger.getLogger(NeaSigningKeyResolver.class.getName());
+
+   // FIXME to config file
+   //final String pubkeyURL = "https://sso.neanias.eu/auth/realms/neanias-development";
+   //final String keysURL = pubkeyURL + "/protocol/openid-connect/certs";
+   // OR:
+   // final String realmName = "skao-devel"; --> url= .../realms/ + realm + /protocol/openid-connect/...
+   String pubkeyURL = "https://sso.neanias.eu/auth/realms/neanias-production";
+   String keysURL = pubkeyURL + "/protocol/openid-connect/certs";
+   // from ESc email: https://sso.neanias.eu/auth/realms/neanias-production/protocol/openid-connect/auth
+
+   NeaSigningKeyResolver(String keysUrl) {this.keysURL = keysUrl;}
+
+   private String doHttps() throws Exception
+   {
+      LOGGER.info("doHttps : " + keysURL);
+
+      URL myUrl = new URL(keysURL);
+      HttpsURLConnection conn = (HttpsURLConnection)myUrl.openConnection();
+      InputStream is = conn.getInputStream();
+      InputStreamReader isr = new InputStreamReader(is);
+      BufferedReader br = new BufferedReader(isr);
+
+      String inputLine;
+      String jsonKeys = ""; 
+      while ((inputLine = br.readLine()) != null) {
+         jsonKeys = jsonKeys + inputLine;
+      }
+
+      br.close();
+
+      return jsonKeys;
+   }
+
+
+
+
+   // deserialize keys
+
+   @JsonAutoDetect(fieldVisibility=JsonAutoDetect.Visibility.ANY)
+   static class NeaKey 
+   {
+      String kid;
+      String kty;
+      String alg;
+      String use;
+      String n;
+      String e;
+      String[] x5c;
+      String x5t;
+      @JsonProperty("x5t#S256") String x5t_S256;
+      }
+
+
+   private String getCertFromKeys(String jsonKeys, String keyId)
+         throws JsonProcessingException, GeneralSecurityException, IOException
+      {
+         LOGGER.info( "NeaSigningKeyResolver::getCertFromKeys");
+
+         ObjectMapper mapper = new ObjectMapper();
+
+         String cert = null;
+
+         JsonNode keysNode = mapper.readTree(jsonKeys).get("keys");
+         if(keysNode.isArray())
+         {
+            for (JsonNode node : keysNode)
+            {
+               String nodeContent = mapper.writeValueAsString(node);
+               NeaKey key = mapper.readValue(nodeContent,NeaKey.class);
+
+               LOGGER.info("keyId    : " + keyId
+                     +"\nKey::kid : " + key.kid);
+
+               if(keyId.equals(key.kid))
+               {
+                  cert = key.x5c[0];
+               }
+            }
+         }
+
+         return cert;
+      }
+
+
+
+
+   private PublicKey getPublicKeyFromPemCert(String certBase64)
+         throws GeneralSecurityException
+      {
+         LOGGER.info( "NeaSigningKeyResolver::getPublicKeyFromPemCert");
+
+         CertificateFactory fac = CertificateFactory.getInstance("X509");
+         ByteArrayInputStream in = new ByteArrayInputStream(Base64.getDecoder().decode(certBase64));
+         X509Certificate cert = (X509Certificate)fac.generateCertificate(in);
+         return cert.getPublicKey();
+      }
+
+
+
+
+   private Key lookupVerificationKey(String keyId)
+         throws Exception, GeneralSecurityException
+      {
+         LOGGER.info( "NeaSigningKeyResolver::lookupVerificationKey" );
+
+         String jsonKeys = doHttps();
+
+         String cert = getCertFromKeys(jsonKeys, keyId);
+
+         PublicKey pubKey = (PublicKey)getPublicKeyFromPemCert(cert);
+
+         return pubKey;
+      }
+
+
+
+
+   @Override
+   public Key resolveSigningKey(JwsHeader jwsHeader, Claims claims)
+   {
+      LOGGER.info( "NeaSigningKeyResolver::resolveSigningKey" );
+
+      //inspect the header or claims, lookup and return the signing key
+
+      String keyId = jwsHeader.getKeyId(); //or any other field that you need to inspect
+
+      Key key = null;
+      try
+      {
+         key = lookupVerificationKey(keyId); //implement me
+      }
+      catch(Exception e)
+      {
+         e.printStackTrace();
+      }
+
+      return key;
+   }
+}
+
diff --git a/auth/src/main/java/NeaTokenFilter.java b/auth/src/main/java/NeaTokenFilter.java
new file mode 100644
index 0000000000000000000000000000000000000000..f24dc4c3ab688d2c096083e403991d97af58a3af
--- /dev/null
+++ b/auth/src/main/java/NeaTokenFilter.java
@@ -0,0 +1,241 @@
+
+import io.jsonwebtoken.JwtException;
+import io.jsonwebtoken.InvalidClaimException;
+import io.jsonwebtoken.Header;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jwt;
+import io.jsonwebtoken.Jws;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.jackson.io.JacksonDeserializer;
+
+import java.security.spec.InvalidKeySpecException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.servlet.http.HttpServletRequestWrapper;
+import java.security.Principal;
+
+import java.io.IOException;
+import java.util.List; // ArrayList<String>
+import java.util.Map;
+import java.util.HashMap;
+import java.util.*;
+
+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 NeaSigningKeyResolver;
+
+public class NeaTokenFilter implements Filter
+{
+   private static final Logger LOGGER = Logger.getLogger("NeaTokenFilter");
+   private static final NeaTokenSettings settings = NeaTokenSettings.getInstance();
+
+   final String resourceId = settings.security.resourceId; //"vlkb"
+   //final String realmName = "neanias-production";
+   //final String keysUrl = "https://sso.neanias.eu/auth/realms/" + realmName + "/protocol/openid-connect/certs";
+   final String keysUrl = settings.security.jwksEndpoint;
+
+   @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;
+
+   String  qString = request.getQueryString();
+   if(qString == null)
+      LOGGER.info(request.getRequestURL().toString());
+   else
+      LOGGER.info(request.getRequestURL() + "    " + qString);
+
+   String authHeader = request.getHeader("Authorization");
+   if (authHeader == null)
+   {
+      boolean non_authenticated_request = (settings.security.non_authn_username != null);
+ 
+      if(non_authenticated_request)
+      {
+         chain.doFilter(request, response);
+      }
+      else
+      {
+         LOGGER.info("Request without Authorization header, no Principal added");
+         response.sendError(HttpServletResponse.SC_BAD_REQUEST,
+               "No Authorization in HTTP-header. Only authorized requests allowed.");
+      }
+      return;
+   }
+   else
+   {
+
+      if (authHeader.startsWith("Bearer ") && (authHeader.length() > "Bearer ".length()))
+      {
+         LOGGER.info("Request with Authorization header and has Bearer entry");
+
+         String jws = authHeader.substring("Bearer ".length());
+
+         try
+         {
+            VlkbUser user = getUserFromAccessToken(jws);
+
+            HttpServletRequestWrapper requestWithPrincipal
+               = new RequestWithPrincipal(request, user);
+
+            chain.doFilter(requestWithPrincipal, response);
+            return;
+
+         }
+         catch (JwtException | InvalidTokenException ex)
+         {
+            LOGGER.warning("Token invalid: " + ex.toString());
+            response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Token invalid");
+            return;
+         }
+         catch (Exception ex)
+         {
+            LOGGER.severe(ex.toString());
+            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                  "Error during authorization");
+            return;
+         }
+
+      }
+      else
+      {
+         LOGGER.warning("Request with Authorization header but without Bearer token");
+
+         response.sendError(HttpServletResponse.SC_BAD_REQUEST,
+               "Only Bearer authorization supported or token missing");
+         return;
+      }
+
+   }
+}
+
+
+private boolean isMapStringObject(Object obj)
+{
+   if(obj instanceof Map)
+   {
+      Map map = (Map)obj;
+
+      Set<?> s = map.keySet();
+      Iterator<?> it = s.iterator();
+      while(it.hasNext())
+      {
+         Object el = it.next();
+         if(! (el instanceof String))
+         {
+            return false;
+         }
+      }
+
+      return true;
+   }
+   else
+      return false;
+}
+
+// validate and parse the token
+
+private List<String> validateAndParseRoles(Claims claims)
+{
+   Map mapobj = null;
+
+   Object obj = claims.get("resource_access");
+   if(isMapStringObject(obj))
+      //if(obj instanceof Map<?,?>)
+      mapobj = (Map)obj;//claims.get("resource_access");
+
+   //////////////////////////////////////////////////////////////////////
+
+   Map<String,Object> resourceAccess = (Map<String, Object>)claims.get("resource_access");
+
+   if(resourceAccess != null)
+   {
+
+      Map<String, Object> resource = (Map<String, Object>)resourceAccess.get(resourceId);
+      if (resource != null)
+      {
+         List<String> roles = (List<String>)resource.get("roles");
+         return roles;
+      }
+      else
+      {
+         LOGGER.warning("Token invalid: 'resource_access' must have value: " + resourceId);
+         throw new InvalidTokenException(
+               "missing 'resource_access' must have value: " + resourceId);
+      }
+   }
+   else
+   {
+      LOGGER.warning("Token invalid: missing 'resource_access' claim");
+      throw new InvalidTokenException("missing 'resource_access' claim");
+   }
+}
+
+
+
+VlkbUser getUserFromAccessToken(String jwsString)
+   //throws JwtException, InvalidTokenException  <-- FIXME
+{
+   long clockSkew = 3 * 60; //3 minutes FIXME get from Config file
+
+   Jws<Claims> jws = Jwts
+      .parser()
+      .setAllowedClockSkewSeconds(clockSkew) // FIXME needed ?
+      .setSigningKeyResolver(new NeaSigningKeyResolver(keysUrl))
+      .build()
+      .parseClaimsJws(jwsString);
+
+   Claims claims = jws.getBody();
+
+   List<String> roles = validateAndParseRoles(claims);
+
+   // set User/Principal
+
+   VlkbUser user = new VlkbUser();
+   user.setAccessToken(jwsString);
+   user.setExpirationTime(0);//FIXME
+   user.setUserId((String) claims.get("sub"));
+   user.setUserLabel((String) claims.get("name"));
+   user.setGroups(roles);
+
+   return user;
+}
+
+
+
+
+
+private static class RequestWithPrincipal extends HttpServletRequestWrapper
+{
+   private final VlkbUser user;
+
+   public RequestWithPrincipal(HttpServletRequest request, VlkbUser user)
+   {
+      super(request);
+      this.user = user;
+   }
+
+   @Override
+   public Principal getUserPrincipal() {
+      return user;
+   }
+}
+
+}
+
diff --git a/auth/src/main/java/NeaTokenSettings.java b/auth/src/main/java/NeaTokenSettings.java
new file mode 100644
index 0000000000000000000000000000000000000000..ce71c442a1d34e917e403ccab445f983f884a5b0
--- /dev/null
+++ b/auth/src/main/java/NeaTokenSettings.java
@@ -0,0 +1,98 @@
+
+import java.util.logging.Logger;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+import java.io.PrintWriter;
+
+/* for Csv-loadSubsurveys * /
+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;
+*/
+
+
+
+class NeaTokenSettings
+{
+   private static final Logger LOGGER = Logger.getLogger("NeaTokenSettings");
+
+   static final String VLKB_PROPERTIES = "neatoken.properties";
+
+   public static class Security
+   {
+      String jwksEndpoint;
+      String resourceId;
+
+      String non_authn_username = null;
+   }
+
+   public Security security;
+
+
+   // will not start without config-file
+   // no reasonable code-defaults can be invented
+   public static NeaTokenSettings getInstance()
+   {
+      try
+      {
+         InputStream ins =
+            NeaTokenSettings.class.getClassLoader().getResourceAsStream(VLKB_PROPERTIES);
+
+         if (ins != null)
+         {
+            Properties properties = new Properties();
+            properties.load(ins);
+
+            Security  security  = loadSecurity(properties);
+
+            return new NeaTokenSettings(security);
+         }
+         else
+         {
+            throw new IllegalStateException(VLKB_PROPERTIES + " not found in classpath");
+         }
+
+      }
+      catch(IOException ex)
+      {
+         throw new IllegalStateException("Error while loading " + VLKB_PROPERTIES + " file", ex);
+      }
+   }
+
+
+
+/* FIXME all  fail if reurned string is null  */
+
+
+   private NeaTokenSettings(Security security)
+   {
+      this.security  = security;
+   }
+
+
+   private static Security loadSecurity(Properties properties)
+   {
+      Security security = new NeaTokenSettings.Security();
+      security.jwksEndpoint = getPropertyStriped(properties, "jwks_url");
+      security.resourceId = getPropertyStriped(properties, "resource_id");
+      security.non_authn_username = getPropertyStriped(properties, "non_authenticated_username");
+      return security;
+   }
+
+
+   private static String getPropertyStriped(Properties properties, String setting)
+   {
+      String st = properties.getProperty(setting);
+      if(st != null) return st.strip();
+      else return st;
+   }
+
+
+}
+
diff --git a/auth/src/main/java/VlkbUser.java b/auth/src/main/java/VlkbUser.java
new file mode 100644
index 0000000000000000000000000000000000000000..6691c03693d6aa541c2ff6e9ea89287d3dfba768
--- /dev/null
+++ b/auth/src/main/java/VlkbUser.java
@@ -0,0 +1,96 @@
+
+import java.security.Principal;
+import java.util.List;
+
+public class VlkbUser implements Principal {
+
+    private String userId;
+    private String userLabel;
+    private String accessToken;
+    private String idToken;
+    private String refreshToken;
+    private long expirationTime;
+    private List<String> groups;
+
+    @Override
+    public String getName() {
+        return userId;
+    }   
+
+    public VlkbUser setUserId(String userId) {
+        this.userId = userId;
+        return this;
+    }   
+
+    public String getUserLabel() {
+        return userLabel;
+    }   
+
+    public VlkbUser setUserLabel(String userLabel) {
+        this.userLabel = userLabel;
+        return this;
+    }   
+
+    public String getAccessToken() {
+        return accessToken;
+    }   
+
+    public VlkbUser setAccessToken(String accessToken) {
+        this.accessToken = accessToken;
+        return this;
+    }   
+
+    public VlkbUser setRefreshToken(String refreshToken) {
+        this.refreshToken = refreshToken;
+        return this;
+    }   
+
+    public String getRefreshToken() {
+        return refreshToken;
+    }   
+
+    public String getIdToken() {
+        return idToken;
+    }   
+
+    public VlkbUser setIdToken(String idToken) {
+        this.idToken = idToken;
+        return this;
+    }
+
+    public long getExpirationTime() {
+        return expirationTime;
+    }
+
+    public VlkbUser setExpirationTime(long expirationTime) {
+        this.expirationTime = expirationTime;
+        return this;
+    }
+
+    public boolean isTokenExpired() {
+        return getExpiresIn() < 0;
+    }
+
+    public long getExpiresIn() {
+        return expirationTime - System.currentTimeMillis() / 1000;
+    }
+
+    public VlkbUser setExpiresIn(int expiresIn) {
+        this.expirationTime = System.currentTimeMillis() / 1000 + expiresIn;
+        return this;
+    }
+
+    public List<String> getGroups() {
+        return groups;
+    }
+
+    public String[] getGroupsAsArray() {
+        return groups.toArray(new String[0]);
+    }
+
+
+    public void setGroups(List<String> groups) {
+        this.groups = groups;
+    }
+}
+
diff --git a/auth/src/test/java/Main.java b/auth/src/test/java/Main.java
new file mode 100644
index 0000000000000000000000000000000000000000..a97f4542c66c8f969c3bd487fcb2d19b22d41c07
--- /dev/null
+++ b/auth/src/test/java/Main.java
@@ -0,0 +1,43 @@
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+
+class Main
+{
+   public static void main(String[] args)
+   {
+      System.out.println("calling Introspect");
+
+      final String tokenFilename = args[0]==null ? "token.base64" : args[0];
+
+      final String USER = "02cc260f-9837-4907-b2cb-a1a2d764fb15";
+      final String PWD  = "AJMi3qrB6AHRp_6y55tEwU-IpJ8uZ6X4QXeQ3W4la6dc-BlkzAY1OQpAE9hb1W7-VfYl4208FUtjE2Cl3hUYLkQ";
+      final String IEP = "https://iam-escape.cloud.cnaf.infn.it/introspect";
+
+      try
+      {
+         final String TOKEN = new String(Files.readAllBytes(Paths.get(tokenFilename)));
+
+         IntrospectResponse ir = new IntrospectResponse(USER+":"+PWD, IEP, TOKEN);
+
+         System.out.println("active: " +  ir.active );
+         System.out.println("scope : " +  ir.scope );
+         if(ir.active)
+            System.out.println("IR: " + ir.getPathFromStorageReadScope());
+      }
+      catch(IOException e)
+      {
+         System.out.println( "EXCPT: " + e.getMessage() );
+         e.printStackTrace();
+      }
+      catch(Exception e)
+      {
+         System.out.println( "EXCPT: " + e.getMessage() );
+         e.printStackTrace();
+      }
+
+
+
+   }
+}
diff --git a/data-discovery/Makefile b/data-discovery/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..02df758ca8a5da6f239aaa6d81ded4365c3f99cd
--- /dev/null
+++ b/data-discovery/Makefile
@@ -0,0 +1,88 @@
+#===============================================================================
+NAME       := vlkb-search
+VERSION    := $(shell git describe)
+WEBAPP_WAR := $(NAME)-$(VERSION).war
+CONTEXT_ROOT ?= vlkb/datasets/vlkb_search
+#===============================================================================
+LIB_DIR     ?= ../java-libs/lib
+CLASS_DIR   := target/classes
+INSTALL_DIR ?= target/webapp
+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/vlkb/:src/main/java/vlkb/common:src/main/java/vlkb/output:src/main/java/vlkb/search:src/main/java/vlkb/webapi:$(AUTH_DIR)/src/main/java
+VOSI     = src/main/java/vlkb/vosi/VlkbServletFile.java
+FILTERS  = $(wildcard src/main/java/vlkb/webapi/*Filter.java)
+SERVLETS = $(wildcard src/main/java/vlkb/webapi/*Servlet.java)
+#===============================================================================
+JFLAGS = -g
+CLASSPATH = $(LIB_DIR)/*
+#===============================================================================
+
+.PHONY: build
+build:
+	echo "class Version { static String asString = \"$(VERSION)\";}" > src/main/java/vlkb/Version.java
+	javac $(JFLAGS) -cp :$(CLASSPATH) -sourcepath $(SRC_DIR) -d $(CLASS_DIR) $(SERVLETS) $(FILTERS) $(AUTHFILTERS) $(VOSI)
+
+.PHONY: clean
+clean : 
+	rm -fr src/main/java/vlkb/Version.java target
+
+
+.PHONY: install
+install:
+	mkdir -p $(INSTALL_DIR)
+	cp -r src/main/webapp/* $(INSTALL_DIR)
+	cp -r $(CLASS_DIR) $(LIB_DIR) $(INSTALL_DIR)/WEB-INF/
+	cp ../java-libs/jjwt-*0.11.2.jar $(INSTALL_DIR)/WEB-INF/lib/
+	cp src/main/resources/*.properties $(INSTALL_DIR)/WEB-INF/classes/
+	cp $(AUTH_DIR)/resources/*.properties $(INSTALL_DIR)/WEB-INF/classes/
+
+.PHONY: uninstall
+uninstall:
+	rm -rf $(INSTALL_DIR)
+
+
+.PHONY: war
+war:
+	@jar -cf target/$(WEBAPP_WAR) -C $(INSTALL_DIR) index.html \
+			  $(INSTALL_DIR)/*.xsl \
+			  $(INSTALL_DIR)/*.js \
+			  $(INSTALL_DIR)/WEB-INF/*.xml \
+			  $(INSTALL_DIR)/WEB-INF/classes/* \
+			  $(INSTALL_DIR)/WEB-INF/lib/*.jar
+
+.PHONY:
+create-war: clean build install war
+
+
+# vlkb-devel host local:
+
+.PHONY: vlkb-devel
+vlkb-devel: stop uninstall clean build install config war start
+
+.PHONY: config
+config:
+	cp config/*.ini $(INSTALL_DIR)/WEB-INF/classes
+	cp config/*.properties $(INSTALL_DIR)/WEB-INF/classes
+	cp config/web.xml $(INSTALL_DIR)/WEB-INF/
+
+
+.PHONY: start
+start:
+	curl -T target/$(WEBAPP_WAR) -u admin:IA2lbt09 'http://localhost:8080/manager/text/deploy?path=/$(CONTEXT_ROOT)&update=true'
+
+.PHONY: stop
+stop:
+	-@curl -u  admin:IA2lbt09 'http://localhost:8080/manager/text/undeploy?path=/$(CONTEXT_ROOT)'
+
+.PHONY: status 
+status:
+	curl localhost:8080/manager/text/list -u admin:IA2lbt09
+
+.PHONY: reload
+reload:
+	curl -u  admin:IA2lbt09  'http://localhost:8080/manager/text/reload?path=/$(CONTEXT_ROOT)'
+
diff --git a/data-discovery/Makefile.m2-libs b/data-discovery/Makefile.m2-libs
new file mode 100644
index 0000000000000000000000000000000000000000..e04bcfcf851de3ab9cfc84805e65769ce6733c3d
--- /dev/null
+++ b/data-discovery/Makefile.m2-libs
@@ -0,0 +1,93 @@
+################################################################################
+# args
+WEBAPP_WAR ?= vlkb-datasetssearch.war
+AUTH ?=
+VERSION ?= $(shell git describe)
+################################################################################
+# target
+################################################################################
+# insternal
+JSRC = java/vlkb
+ROOT = webapp
+AUTH = ../../../auth
+#META_DIR  = webapp/META-INF
+#CLASS_DIR = webapp/WEB-INF/classes
+MVN_REPO = ~/.m2/repository
+################################################################################
+
+all: build
+
+libs-m2:
+	mkdir -p $(JSRC)/lib
+	cp $(MVN_REPO)/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar $(JSRC)/lib
+	cp $(MVN_REPO)/com/opencsv/opencsv/5.7.1/opencsv-5.7.1.jar $(JSRC)/lib
+	cp $(MVN_REPO)/it/inaf/ia2/auth-lib/2.0.0-SNAPSHOT/auth-lib-2.0.0-SNAPSHOT.jar $(JSRC)/lib
+	cp $(MVN_REPO)/it/inaf/ia2/rap-client/1.0-SNAPSHOT/rap-client-1.0-SNAPSHOT.jar $(JSRC)/lib
+	cp $(MVN_REPO)/org/postgresql/postgresql/42.6.0/postgresql-42.6.0.jar $(JSRC)/lib
+	cp $(MVN_REPO)/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar $(JSRC)/lib
+	cp $(MVN_REPO)/org/slf4j/slf4j-simple/1.7.36/slf4j-simple-1.7.36.jar $(JSRC)/lib
+	cp $(MVN_REPO)/ch/qos/logback/logback-core/1.4.7/logback-core-1.4.7.jar  $(JSRC)/lib
+	cp $(MVN_REPO)/ch/qos/logback/logback-classic/1.4.7/logback-classic-1.4.7.jar $(JSRC)/lib
+	cp $(MVN_REPO)/com/fasterxml/jackson/core/jackson-databind/2.15.1/jackson-databind-2.15.1.jar $(JSRC)/lib
+	cp $(MVN_REPO)/com/fasterxml/jackson/core/jackson-annotations/2.15.1/jackson-annotations-2.15.1.jar $(JSRC)/lib
+	cp $(MVN_REPO)/com/fasterxml/jackson/core/jackson-core/2.15.1/jackson-core-2.15.1.jar $(JSRC)/lib
+
+
+libs:
+	mkdir -p $(JSRC)/lib
+	cp ../../../java-libs/lib/*.jar $(JSRC)/lib
+	cp $(AUTH)/lib/*.jar $(JSRC)/lib
+
+build-auth:
+	make -C $(AUTH) build
+
+clean-auth:
+	make -C $(AUTH) clean
+
+
+clean-libs: clean-auth
+	-rm -fr $(JSRC)/lib
+
+
+.PHONY: build
+build: build-auth libs
+	make -C $(JSRC) VERSION=$(VERSION)
+	-rm -fr $(ROOT)/WEB-INF/classes
+	-rm -fr $(ROOT)/WEB-INF/lib
+	cp -r $(JSRC)/classes $(ROOT)/WEB-INF/
+	cp -r $(JSRC)/lib $(ROOT)/WEB-INF/
+
+clean: clean-libs
+	make -C $(JSRC) clean
+	rm -fr $(ROOT)/WEB-INF/classes
+	rm -fr $(ROOT)/WEB-INF/lib
+
+
+
+
+.PHONY: war
+war:
+	cat resources/web.xml-begining resources/web.xml-format-filter resources/web.xml-$(AUTH)-filter  resources/web.xml-authorization-filter resources/web.xml-servlets resources/web.xml-ending > $(ROOT)/WEB-INF/web.xml
+	#cat resources/web.xml-begining resources/web.xml-servlets resources/web.xml-ending > $(ROOT)/WEB-INF/web.xml
+	@jar -cf $(WEBAPP_WAR) -C $(ROOT) index.html \
+      $(ROOT)/*.xsl \
+      $(ROOT)/*.js \
+      $(ROOT)/WEB-INF/*.xml \
+      $(ROOT)/WEB-INF/classes/* \
+      $(ROOT)/WEB-INF/lib/*.jar
+	@ls -l $(WEBAPP_WAR)
+
+
+.PHONY: uninstall
+uninstall : 
+	rm -f $(ROOT)/WEB-INF/*.xml* $(WEBAPP_WAR) 
+
+
+.PHONY: test
+test:
+	@echo "WEBAPP_WAR : "$(WEBAPP_WAR)
+	@echo "ROOT       : "$(ROOT)
+
+.PHONY: testwar
+testwar:
+	jar -tf $(WEBAPP_WAR)
diff --git a/data-discovery/config/Makefile b/data-discovery/config/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..e03b2d63c7dc9bcf8f5431d7e5940444ea4910ca
--- /dev/null
+++ b/data-discovery/config/Makefile
@@ -0,0 +1,19 @@
+################################################################
+DBMS   ?= localhost  # localhost | pasquale | pasquale-devel
+AUTH   ?=            # ia2token | garrtoken | <empty>
+################################################################
+
+all: authpolicy.properties web.xml discovery.properties
+
+web.xml:
+	cd web-xml && cat web.xml-begining web.xml-format-filter web.xml-$(AUTH)-filter web.xml-authorization-filter web.xml-servlets web.xml-ending > ../web.xml
+
+discovery.properties: discovery.properties.in
+	cat dbms.conf-$(DBMS) discovery.properties.in > $@
+
+authpolicy.properties: dbms.conf-$(DBMS)
+	cp dbms.conf-$(DBMS) $@
+
+.PHONY:
+clean:
+	-rm -f authpolicy.properties discovery.properties web.xml
diff --git a/data-discovery/config/auth.properties b/data-discovery/config/auth.properties
new file mode 100644
index 0000000000000000000000000000000000000000..c9c8aee27f0017b03a10a17896236eae4a93a018
--- /dev/null
+++ b/data-discovery/config/auth.properties
@@ -0,0 +1,10 @@
+rap_uri=https://sso.ia2.inaf.it/rap-ia2
+gms_uri=https://sso.ia2.inaf.it/gms
+client_id=vospace_ui_demo
+client_secret=VOSpaceDemo123
+
+groups_autoload=true
+store_state_on_login_endpoint=true
+scope=openid email profile read:rap
+
+allow_anonymous_access=true
diff --git a/data-discovery/config/dbms.conf-localhost b/data-discovery/config/dbms.conf-localhost
new file mode 100644
index 0000000000000000000000000000000000000000..1c59ef6ea99316ff778ca7dda6cb2cb3493aa9b3
--- /dev/null
+++ b/data-discovery/config/dbms.conf-localhost
@@ -0,0 +1,6 @@
+db_uri=jdbc:postgresql://127.0.0.1:5432/vialactea
+db_schema=datasets
+db_user_name=vialactea
+db_password=ia2vlkb
+
+
diff --git a/data-discovery/config/dbms.conf-pasquale b/data-discovery/config/dbms.conf-pasquale
new file mode 100644
index 0000000000000000000000000000000000000000..20542f00a2ec1138ba6f2d498851d77f99a8e9c3
--- /dev/null
+++ b/data-discovery/config/dbms.conf-pasquale
@@ -0,0 +1,6 @@
+db_uri=jdbc:postgresql://pasquale.ia2.inaf.it:5432/vialactea
+db_schema=datasets
+db_user_name=vialactea
+db_password=ia2vlkb
+
+
diff --git a/data-discovery/config/dbms.conf-pasquale-devel b/data-discovery/config/dbms.conf-pasquale-devel
new file mode 100644
index 0000000000000000000000000000000000000000..1a16c5bd941617c74e3c98e9ef469133452bb2ef
--- /dev/null
+++ b/data-discovery/config/dbms.conf-pasquale-devel
@@ -0,0 +1,7 @@
+db_uri=jdbc:postgresql://pasquale.ia2.inaf.it:5432/vialacteadevel
+db_port=5432
+db_schema=datasetsdevel
+db_user_name=vialactea
+db_password=ia2vlkb
+
+
diff --git a/data-discovery/config/discovery.properties.in b/data-discovery/config/discovery.properties.in
new file mode 100644
index 0000000000000000000000000000000000000000..49dc0fbc259a9d7a8ca045a4daa741e52b70027d
--- /dev/null
+++ b/data-discovery/config/discovery.properties.in
@@ -0,0 +1,12 @@
+
+
+# database with 'surveys' table of metadata used to write VLKB-legacy response.xml
+#db_uri=jdbc:postgresql://localhost:5432/vialactea
+#db_schema=datasets
+#db_user_name=vialactea
+#db_password=IA2lbt09
+
+# obs_publisher_did (FIXME used only in (Db)AuthPolicy while database access to convert between legacy pubdid and ivoid)
+#ivoid_authority=ia2.inaf.it
+#ivoid_resource_key=vlkb/datasets
+
diff --git a/data-discovery/config/formatresponsefilter.properties b/data-discovery/config/formatresponsefilter.properties
new file mode 100644
index 0000000000000000000000000000000000000000..1d03f40385f33730b30f870330442109e27fe150
--- /dev/null
+++ b/data-discovery/config/formatresponsefilter.properties
@@ -0,0 +1,7 @@
+
+# used to retrieve extraCards to add to FITS_header (VLKB-only)
+surveys_metadata_abs_pathname=/srv/vlkb/surveys/survey_populate.csv
+
+# these URL's are used to construct cutout merge requests strings in response.xml
+cutout_url=http://vlkb-devel.ia2.inaf.it:8080/vlkb/datasets/vlkb_cutout
+merge_url=http://vlkb-devel.ia2.inaf.it:8080/vlkb/datasets/vlkb_merge
diff --git a/data-discovery/config/iamtoken.properties b/data-discovery/config/iamtoken.properties
new file mode 100644
index 0000000000000000000000000000000000000000..e0935bb1f2d6f832b04b22c9dac817eac6741e5d
--- /dev/null
+++ b/data-discovery/config/iamtoken.properties
@@ -0,0 +1,10 @@
+
+#jwks_url=https://iam-escape.cloud.cnaf.infn.it/jwk
+introspect=https://iam-escape.cloud.cnaf.infn.it/introspect
+client_name=02cc260f-9837-4907-b2cb-a1a2d764fb15
+client_password=AJMi3qrB6AHRp_6y55tEwU-IpJ8uZ6X4QXeQ3W4la6dc-BlkzAY1OQpAE9hb1W7-VfYl4208FUtjE2Cl3hUYLkQ
+
+resource_id=vlkb
+
+non_authn_username=anonymous
+
diff --git a/data-discovery/config/neatoken.properties b/data-discovery/config/neatoken.properties
new file mode 100644
index 0000000000000000000000000000000000000000..21793e2600441bc6122e1ce54387ad8525bbd297
--- /dev/null
+++ b/data-discovery/config/neatoken.properties
@@ -0,0 +1,7 @@
+
+jwks_url=https://sso.neanias.eu/auth/realms/neanias-production/protocol/openid-connect/certs
+
+resource_id=vlkb
+
+non_authn_username=anonymous
+
diff --git a/data-discovery/config/shiro.ini b/data-discovery/config/shiro.ini
new file mode 100644
index 0000000000000000000000000000000000000000..4324a25031ec4eab8cdd1b1aa288afd17f3300eb
--- /dev/null
+++ b/data-discovery/config/shiro.ini
@@ -0,0 +1,40 @@
+#
+# Copyright (c) 2013 Les Hazlewood and contributors
+#
+# 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.
+#
+
+# INI configuration is very powerful and flexible, while still remaining succinct.
+# Please http://shiro.apache.org/configuration.html and
+# http://shiro.apache.org/web.html for more.
+
+[main]
+shiro.loginUrl = /login.jsp
+cacheManager = org.apache.shiro.cache.MemoryConstrainedCacheManager
+securityManager.cacheManager = $cacheManager
+#securityManager.realm = $stormpathRealm
+
+[users]
+# syntax: user = password , roles
+vialactea = ia2vlkb, ROLE_ADMIN
+
+[roles]
+ROLE_ADMIN = *
+
+[urls]
+#/login.jsp = authc
+/logout = logout
+/** = authcBasic
+#/ivoa/resources/basic/** = authcBasic
+#/ivoa/resources/full/** = authc
+
diff --git a/data-discovery/config/web-xml/web.xml--filter b/data-discovery/config/web-xml/web.xml--filter
new file mode 100644
index 0000000000000000000000000000000000000000..84ce5ae3c3463d026d04c046efb727363623087c
--- /dev/null
+++ b/data-discovery/config/web-xml/web.xml--filter
@@ -0,0 +1,3 @@
+
+<!-- no authorization filter configured -->
+
diff --git a/data-discovery/config/web-xml/web.xml-authorization-filter b/data-discovery/config/web-xml/web.xml-authorization-filter
new file mode 100644
index 0000000000000000000000000000000000000000..94b9d135064567f55dbc504c71ef99dca45c0177
--- /dev/null
+++ b/data-discovery/config/web-xml/web.xml-authorization-filter
@@ -0,0 +1,9 @@
+        <filter>
+                <filter-name>AuthorizationResponseFilter</filter-name>
+                <filter-class>AuthorizationResponseFilter</filter-class>
+        </filter>
+        <filter-mapping>
+                <filter-name>AuthorizationResponseFilter</filter-name>
+                <url-pattern>/*</url-pattern>
+        </filter-mapping>
+
diff --git a/data-discovery/config/web-xml/web.xml-begining b/data-discovery/config/web-xml/web.xml-begining
new file mode 100644
index 0000000000000000000000000000000000000000..cb551fabbc25b697e0340cdee9d22cc51115c461
--- /dev/null
+++ b/data-discovery/config/web-xml/web.xml-begining
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Copyright 2004-2005 Sun Microsystems, Inc.  All rights reserved.
+ Use is subject to license terms.
+-->
+
+<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
+        <display-name>Via Lactea. Query FITS datacubes.</display-name>
+        <distributable/>
+
+
diff --git a/data-discovery/config/web-xml/web.xml-ending b/data-discovery/config/web-xml/web.xml-ending
new file mode 100644
index 0000000000000000000000000000000000000000..9d1c7ed2dc6d32a1171a8a7a4ff16eacb13312b2
--- /dev/null
+++ b/data-discovery/config/web-xml/web.xml-ending
@@ -0,0 +1 @@
+</web-app>
diff --git a/data-discovery/config/web-xml/web.xml-format-filter b/data-discovery/config/web-xml/web.xml-format-filter
new file mode 100644
index 0000000000000000000000000000000000000000..d63cd024aa92f6ae6780845a08f674c29f6e8ff3
--- /dev/null
+++ b/data-discovery/config/web-xml/web.xml-format-filter
@@ -0,0 +1,9 @@
+        <filter>
+                <filter-name>FormatResponseFilter</filter-name>
+                <filter-class>FormatResponseFilter</filter-class>
+        </filter>
+        <filter-mapping>
+                <filter-name>FormatResponseFilter</filter-name>
+                <url-pattern>/*</url-pattern>
+        </filter-mapping>
+
diff --git a/data-discovery/config/web-xml/web.xml-garrtoken-filter b/data-discovery/config/web-xml/web.xml-garrtoken-filter
new file mode 100644
index 0000000000000000000000000000000000000000..98887caf089c643ef0a583eb58ef8061a64aa5c0
--- /dev/null
+++ b/data-discovery/config/web-xml/web.xml-garrtoken-filter
@@ -0,0 +1,9 @@
+        <filter>
+                <filter-name>TokenFilter</filter-name>
+                <filter-class>NeaTokenFilter</filter-class>
+        </filter>
+        <filter-mapping>
+                <filter-name>TokenFilter</filter-name>
+                <url-pattern>/*</url-pattern>
+        </filter-mapping>
+
diff --git a/data-discovery/config/web-xml/web.xml-ia2token-filter b/data-discovery/config/web-xml/web.xml-ia2token-filter
new file mode 100644
index 0000000000000000000000000000000000000000..7e8326c643702b99bbd05a3db3d342806cbcb7f3
--- /dev/null
+++ b/data-discovery/config/web-xml/web.xml-ia2token-filter
@@ -0,0 +1,21 @@
+
+       <filter>
+               <filter-name>TokenFilter</filter-name>
+               <filter-class>it.inaf.ia2.aa.TokenFilter</filter-class>
+       </filter>
+
+       <filter-mapping>
+               <filter-name>TokenFilter</filter-name>
+               <url-pattern>/*</url-pattern>
+       </filter-mapping>
+
+       <filter>
+               <filter-name>UserTypeConverter</filter-name>
+               <filter-class>IA2TokenConvFilter</filter-class>
+       </filter>
+
+       <filter-mapping>
+               <filter-name>UserTypeConverter</filter-name>
+               <url-pattern>/*</url-pattern>
+       </filter-mapping>
+
diff --git a/data-discovery/config/web-xml/web.xml-monitor-filter b/data-discovery/config/web-xml/web.xml-monitor-filter
new file mode 100644
index 0000000000000000000000000000000000000000..ecefb2e0b3014b08fa18997795b54f118814a852
--- /dev/null
+++ b/data-discovery/config/web-xml/web.xml-monitor-filter
@@ -0,0 +1,9 @@
+        <filter>
+                <filter-name>MonitorFilter</filter-name>
+                <filter-class>MonitorFilter</filter-class>
+        </filter>
+        <filter-mapping>
+                <filter-name>MonitorFilter</filter-name>
+                <url-pattern>/*</url-pattern>
+        </filter-mapping>
+
diff --git a/data-discovery/config/web-xml/web.xml-servlets b/data-discovery/config/web-xml/web.xml-servlets
new file mode 100644
index 0000000000000000000000000000000000000000..6319c328770db32de7a11d413264e57d3217b1e9
--- /dev/null
+++ b/data-discovery/config/web-xml/web.xml-servlets
@@ -0,0 +1,32 @@
+
+
+<servlet>
+   <servlet-name>vlkb_search</servlet-name>
+   <servlet-class>SearchServlet</servlet-class>
+</servlet>
+<servlet-mapping>
+   <servlet-name>vlkb_search</servlet-name>
+   <url-pattern></url-pattern>
+</servlet-mapping>
+
+
+<servlet>
+   <servlet-name>vlkb_vosi_availability</servlet-name>
+   <servlet-class>VlkbServletFile</servlet-class>
+</servlet>
+<servlet-mapping>
+   <servlet-name>vlkb_vosi_availability</servlet-name>
+   <url-pattern>/availability</url-pattern>
+</servlet-mapping>
+
+
+<servlet>
+   <servlet-name>vlkb_vosi_capabilities</servlet-name>
+   <servlet-class>VlkbServletFile</servlet-class>
+</servlet>
+<servlet-mapping>
+   <servlet-name>vlkb_vosi_capabilities</servlet-name>
+   <url-pattern>/capabilities</url-pattern>
+</servlet-mapping>
+
+
diff --git a/data-discovery/mvn-install-local-deps.sh b/data-discovery/mvn-install-local-deps.sh
new file mode 100755
index 0000000000000000000000000000000000000000..41ee4fde7b3311b96aa4eddc580e378540b1e2cf
--- /dev/null
+++ b/data-discovery/mvn-install-local-deps.sh
@@ -0,0 +1,20 @@
+
+mvn install:install-file \
+   -Dfile=src/main/java/vlkb/auth/ext/auth-lib-2.0.0-SNAPSHOT.jar \
+   -DgroupId=it.inaf.ia2.aa.data \
+   -DartifactId=auth-lib \
+   -Dversion=2.0.0-SNAPSHOT \
+   -Dpackaging=jar \
+   -DgeneratePom=true
+
+
+
+mvn install:install-file \
+   -Dfile=src/main/java/vlkb/auth/ext/rap-client-1.0-SNAPSHOT.jar \
+   -DgroupId=it.inaf.ia2.rap.data \
+   -DartifactId=rap-client \
+   -Dversion=1.0-SNAPSHOT \
+   -Dpackaging=jar \
+   -DgeneratePom=true
+
+
diff --git a/data-discovery/notes-separate-search-from-dacc.tex b/data-discovery/notes-separate-search-from-dacc.tex
new file mode 100644
index 0000000000000000000000000000000000000000..554ead5e25045e03fdf8a429a95595f39e506d54
--- /dev/null
+++ b/data-discovery/notes-separate-search-from-dacc.tex
@@ -0,0 +1,67 @@
+
+
+commons:
+
+Settings  --> duplicate accepatble --> common-beans lib ?
+
+Subsurvey --> out of the scope of the search-problem/design concern (belongs to presentation-problem): solve by filter run after search, on search results: array of ID's (sewarch problem: based Coord's identify data by ID's (IN: Coords OUT: array os ID's; Subsurvey are needed for presentation of the isearch-result)
+
+
+AuthPolicy - the same as Subsurveys: not in search-problem area ; belongs to seacurity-problem (solve by separate filter run after the search and filtering output ID's)
+
+
+Coord : duplicate acceptable [ truly common -> (vo-beans? small lib of commons?)]
+
+Inputs : (aggregate of all of the above) ???
+
+SubsurveyId : search inut param and result presentation-problem area
+               dacc vlkb-specific: header update feature-related (not part of the strict dacc-problem area)
+
+
+
+
+
+search-servlet --> auth-filter(output-ID array) --> presentation-xml-filter
+
+dacc-servlet --> auth-filter(input-ID array) --> update fits-header
+
+--------------------------------------------
+'Pure' search and 'pure' dacc design:
+
+definitions:
+pure-search operates on ObsCore.   Input: coords Output: list of publisher-did's
+pure-dacc operates on disk-files:  Input: ID + coords Output: cut data in a file
+
+commons:
+
+Settings -> duplicate ok
+
+Subsurvey metadata -> not needed neither pure-search nor pure-dacc
+
+AuthPolicy -> not needed
+
+Coord: true common (use vo-beans lib approach?)
+
+SubsurveyId -> search-only (input-para-param)
+
+
+-------------------------------------------------
+TODO.
+
+SubsurveyMetadata - table holding in memory subsurveys metadata from csv file read at app init.
+                  - provides: extraCards for vlkb-soda to update headers --> needs subsurveyId !!
+                              metadata set for search result-xml
+(put into all Servlet code : Serlvets must not contain any Survey[] array: loadCsv inside SubsurveyMetadata class; not Servlets)
+remains common but later easier to move to Filter
+
+Currently VlkbSql needed in dacc due to resolveByMapping() call using DB to resolve legacy pubdid (generated
+from dir and filename of the fitsfile replacing / and spaces with _ and - respectibely - this process being non-reversible)
+
+RESULT: Subsurvey + SubsurveyId = SubsurveyMetadata and should be active only in vlkb-compatibility mode
+
+Additionally:
+* search results (for vlkb) must be array if pairs: (publisherDid, [subsurveyId]) ?? check SIAv2 : sia should identify rows in ObsCore ??
+* input to dacc (for vlkb-soda) must resolve to hdu or the pair: fitspathanme+hdunnum + [subsurveyID] - if no header-updates, then no subsurveyId needed
+
+
+
diff --git a/data-discovery/pom.xml b/data-discovery/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d59c3a1525d15e7f9b522549f96a9bc5a8e31717
--- /dev/null
+++ b/data-discovery/pom.xml
@@ -0,0 +1,139 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>vlkb</groupId>
+  <artifactId>psearch</artifactId>
+  <packaging>war</packaging>
+  <version>1.0-SNAPSHOT</version>
+  <name>psearch Maven Webapp</name>
+  <url>http://maven.apache.org</url>
+
+  <properties>
+          <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+          <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+          <!-- FIXME needs JAVA_HOME=/usr/java/jdk-17.0.4.1 (java17 manually installed and aletrnatives activated) -->
+          <maven.compiler.source>17</maven.compiler.source>
+          <maven.compiler.target>17</maven.compiler.target>
+  </properties>
+
+
+  <dependencies>
+
+          <!-- local -->
+          <dependency>
+                  <groupId>it.inaf.ia2</groupId>
+                  <artifactId>auth-lib</artifactId>
+                  <version>2.0.0-SNAPSHOT</version>
+                  <scope>provided</scope>
+          </dependency>
+
+          <!-- local -->
+          <dependency>
+                  <groupId>it.inaf.ia2</groupId>
+                  <artifactId>rap-client</artifactId>
+                  <version>1.0-SNAPSHOT</version>
+                  <scope>provided</scope>
+          </dependency>
+          <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
+          <dependency>
+                  <groupId>com.fasterxml.jackson.core</groupId>
+                  <artifactId>jackson-databind</artifactId>
+                  <version>2.15.1</version>
+          </dependency>
+          <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
+          <dependency>
+                  <groupId>com.fasterxml.jackson.core</groupId>
+                  <artifactId>jackson-core</artifactId>
+                  <version>2.15.1</version>
+          </dependency>
+          <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
+          <dependency>
+                  <groupId>com.fasterxml.jackson.core</groupId>
+                  <artifactId>jackson-annotations</artifactId>
+                  <version>2.15.1</version>
+          </dependency>
+
+          <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
+          <dependency>
+                  <groupId>org.slf4j</groupId>
+                  <artifactId>slf4j-api</artifactId>
+                  <version>1.7.36</version>
+          </dependency>
+          <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-simple -->
+          <dependency>
+                  <groupId>org.slf4j</groupId>
+                  <artifactId>slf4j-simple</artifactId>
+                  <version>1.7.36</version>
+                  <scope>test</scope>
+          </dependency>
+
+          <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
+          <dependency>
+                  <groupId>ch.qos.logback</groupId>
+                  <artifactId>logback-classic</artifactId>
+                  <version>1.4.7</version>
+                  <scope>test</scope>
+          </dependency>
+          <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-core -->
+          <dependency>
+                  <groupId>ch.qos.logback</groupId>
+                  <artifactId>logback-core</artifactId>
+                  <version>1.4.7</version>
+          </dependency>
+
+          <dependency>
+                  <groupId>javax.servlet</groupId>
+                  <artifactId>servlet-api</artifactId>
+                  <version>2.5</version>
+                  <scope>provided</scope>
+          </dependency>
+
+          <dependency>
+                  <groupId>com.opencsv</groupId>
+                  <artifactId>opencsv</artifactId>
+                  <version>5.7.1</version>
+                  <scope>provided</scope>
+          </dependency>
+
+          <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
+          <dependency>
+                  <groupId>io.jsonwebtoken</groupId>
+                  <artifactId>jjwt-api</artifactId>
+                  <version>0.11.5</version>
+          </dependency>
+
+          <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson -->
+          <dependency>
+                  <groupId>io.jsonwebtoken</groupId>
+                  <artifactId>jjwt-jackson</artifactId>
+                  <version>0.11.5</version>
+                  <!-- scope>runtime</scope -->
+  </dependency>
+
+  <!-- https://mvnrepository.com/artifact/org.postgresql/postgresql -->
+  <dependency>
+          <groupId>org.postgresql</groupId>
+          <artifactId>postgresql</artifactId>
+          <version>42.6.0</version>
+  </dependency>
+
+  <dependency>
+          <groupId>junit</groupId>
+          <artifactId>junit</artifactId>
+          <version>3.8.1</version>
+          <scope>test</scope>
+  </dependency>
+
+  </dependencies>
+
+  <build>
+          <finalName>psearch</finalName>
+          <plugins>
+                  <plugin>
+                          <artifactId>maven-war-plugin</artifactId>
+                          <version>3.3.2</version>
+                  </plugin>
+          </plugins>
+  </build>
+
+</project>
diff --git a/data-discovery/src/main/java/vlkb/common/Coord.java b/data-discovery/src/main/java/vlkb/common/Coord.java
new file mode 100644
index 0000000000000000000000000000000000000000..f28b423b60d4cfb28f4c324c345bf12f02c112c5
--- /dev/null
+++ b/data-discovery/src/main/java/vlkb/common/Coord.java
@@ -0,0 +1,325 @@
+import java.util.logging.Logger;
+import java.util.Map;
+import java.io.PrintWriter;
+
+class Coord
+{
+   private static final Logger LOGGER = Logger.getLogger(Coord.class.getName());
+
+   String skySystem; // FIXME make enum
+
+   // center
+   double lon;
+   double lat;
+
+   // extent  < EXT_LIMIT = 4deg
+   String shape; // FIXME enum CIRCLE | RECT 
+   double radius;
+   double dlon;
+   double dlat;
+
+   public boolean vel_valid;
+   String vel_type; // FIXME make enum
+   double vel_up;
+   double vel_low;
+
+   // constructors
+   Coord(Map<String, String[]> params)
+   {
+      String id     = getFirstValue(params, "ID");
+      String pubdid = getFirstValue(params, "pubdid");
+
+      if(id != null)
+         parseSoda(params);
+      else
+         parseVlkb(params);
+
+      LOGGER.info("Parse result: " + toQueryString());
+   }
+
+
+   protected void parseSoda(Map<String, String[]> params)
+   {
+      LOGGER.info("trace");
+
+      try
+      {
+         String pubdid   = null;
+         Coord  coord    = null;
+
+
+         pubdid          = Parser.getFirstString(params, "ID");
+         String[] circle = Parser.getFirstStringArray(params,"CIRCLE", " ", 3);
+         String[] vel    = Parser.getFirstStringArray(params,"BAND", " ", 2);
+
+
+         String skySystem = Parser.getFirstString(params, "skysystem"); // FIXME add sanity checks / use enum
+         if(skySystem == null) skySystem = "ICRS";
+
+         this.skySystem = skySystem;
+         this.lon = Double.parseDouble(circle[0]);
+         this.lat = Double.parseDouble(circle[1]);
+         this.radius = Double.parseDouble(circle[2]);
+
+         if(this.radius <= 0.0) throw new IllegalArgumentException("radius must be positive and not zero");
+         this.shape = "CIRCLE";
+
+
+         String specSystem = Parser.getFirstString(params, "specsystem");
+         if(specSystem == null) specSystem = "2"; // 2=WAVE BARY
+         if( (vel != null) && (vel.length >= 2) )
+         {
+            this.vel_type = specSystem; // FIXME add sanity checks / use enum
+
+            if((vel[0] != null) && (vel[1] != null))
+            {
+               this.vel_low = Double.parseDouble(vel[0]);
+               this.vel_up  = Double.parseDouble(vel[1]);
+               this.vel_valid = true;
+            }
+            else
+            {
+               this.vel_valid = false;
+            }
+         }
+      }
+      catch (IllegalArgumentException illArg)
+      {
+         // response.sendError(HttpServletResponse.SC_BAD_REQUEST, "Request with incorrect parameters: " + illArg.getMessage());
+         throw new IllegalArgumentException("Request with incorrect parameters: " + illArg.getMessage());
+      }
+   } 
+
+
+   protected void parseVlkb(Map<String, String[]> params)
+   {
+      LOGGER.info("trace");
+
+      // (l,b) is mandatory 
+
+      this.skySystem = "GALACTIC";
+      String lcl = getFirstValue(params, "l");
+      String lcb = getFirstValue(params, "b");
+
+      if((lcl == null) || (lcb == null))
+         throw new IllegalArgumentException("parameters (l,b) are mandatory."); 
+
+      lon = Double.parseDouble(lcl);
+      lat = Double.parseDouble(lcb);
+
+      // one of: radius r OR (dlon,dlat) is mandatory
+
+      if(params.containsKey("r"))
+      {
+         String lcr = getFirstValue(params, "r");
+         radius = Double.parseDouble(lcr);
+         this.shape = "CIRCLE";
+      }
+      else if (params.containsKey("dl") && params.containsKey("db"))
+      {
+         String lcdl = getFirstValue(params, "dl");
+         String lcdb = getFirstValue(params, "db");
+         dlon = Double.parseDouble(lcdl);
+         dlat = Double.parseDouble(lcdb);
+         this.shape = "RECT";
+      }
+      else
+         throw new IllegalArgumentException("area extent is mandatory: either radius r or (dl,db) must be given."); 
+
+      // velocity is optional
+
+      String cvlow = getFirstValue(params, "vl");
+      String cvup  = getFirstValue(params, "vu");
+      String cvtype = "1"; // VLKB: VELO LSRK
+
+      vel_valid = (cvlow != null) && (cvup != null);
+
+      if(vel_valid)
+      {
+         vel_low = Double.parseDouble(cvlow);
+         vel_up  = Double.parseDouble(cvup);
+         vel_type = cvtype;
+      }
+   }
+
+
+   // if param present -> must have at least one value
+   static private String getFirstValue(Map<String, String[]> map, String key)
+   {
+      String[] value = map.get(key);
+
+      if(value == null)
+         return null;
+
+      if(value.length > 0)    // key-value present at least once: return first occurance
+         return value[0].toString();
+      else                    // length=0: no values present (array exists but is empty)
+         throw new IllegalArgumentException("parameter " + key + " has no value."); 
+   }
+
+
+   Coord(String lon, String lat, String radius, String velLow, String velUp)
+   {
+      try
+      {
+         this.skySystem = "GALACTIC";
+         this.lon = Double.parseDouble(lon);
+         this.lat = Double.parseDouble(lat);
+         this.radius = Double.parseDouble(radius);
+      }
+      catch(Exception e)
+      {
+         throw new IllegalArgumentException("lon and lat are mandatory: " + e.getMessage());
+      }
+
+      if(this.radius <= 0.0) throw new IllegalArgumentException("radius must be positive and not zero");
+
+      this.shape = "CIRCLE";
+
+      if((velLow != null) && (velUp != null))
+      {
+         this.vel_low = Double.parseDouble(velLow);
+         this.vel_up  = Double.parseDouble(velUp);
+         this.vel_type  = "1"; // VELO + LSRK
+         //this.vel_type  = "2"; // WAVE + Barycentric
+         this.vel_valid = true;
+      }
+      else
+      {
+         this.vel_valid = false;
+      }
+   }
+
+
+
+   Coord(double lon, double lat, double radius)
+   {
+      if(radius < 0.0)
+         throw new IllegalArgumentException("radius must be bigger than zero: " + radius); 
+
+      this.skySystem = "GALACTIC";
+      this.lon = lon;
+      this.lat = lat;
+      this.radius = radius;
+      this.shape  = "CIRCLE";
+      this.vel_valid = false;
+   }
+
+   Coord(double lon, double lat, double dlon, double dlat)
+   {
+      if((dlon < 0.0) || (dlat < 0.0))
+         throw new IllegalArgumentException("both dlon and dlat must be bigger than zero: " + dlon + " " + dlat); 
+
+      this.skySystem = "GALACTIC";
+      this.lon  = lon;
+      this.lat  = lat;
+      this.dlon = dlon;
+      this.dlat = dlat;
+      this.shape = "RECT";
+      this.vel_valid = false;
+   }
+
+   void setSkySystem(String skySystem) { this.skySystem = skySystem; }
+   void setSpecSystem(String velType) { this.vel_type = velType; }
+
+   // spectral axis
+
+   void setVelocity(double vel_low, double vel_up, String vel_type)
+   {
+      this.vel_type  = vel_type;
+      this.vel_low   = vel_low;
+      this.vel_up    = vel_up;
+      this.vel_valid = true;
+   }
+
+   // utils
+
+   public String toString()
+   {
+      String area = null;
+      switch(shape)
+      {
+         case "CIRCLE" : area = String.valueOf(radius); break;
+         case "RECT"   : area = dlon + ", " + dlat; break;
+         default: // FIXME leave with exception
+                         area = "err: " + shape;
+      }
+
+      String resourceSearchArea 
+         = "(P; area) = (" + lon + ", " + lat + "; " + area + ") [deg]";
+
+      return resourceSearchArea;
+   }
+
+
+   void toXML(PrintWriter writer)
+   {
+      // center is mandatory -> create no Coord if center not valid
+      writer.println("<SkySystem>"+skySystem+"</SkySystem>");
+      writer.println("<l>"+lon+"</l>");
+      writer.println("<b>"+lat+"</b>");
+
+      switch(shape)
+      {
+         case "CIRCLE" :  writer.println("<r>"+String.valueOf(radius)+"</r>"); break;
+         case "RECT"   :
+                          writer.println("<dl>"+String.valueOf(dlon)+"</dl>");
+                          writer.println("<db>"+String.valueOf(dlat)+"</db>");
+                          break;
+         default:
+                          writer.println("<shape> unknown shape: "+ shape +" </shape>");
+      }
+      if(vel_valid)
+      {
+         writer.println("<vl>"   + String.valueOf(vel_low)  +"</vl>");
+         writer.println("<vu>"   + String.valueOf(vel_up)   +"</vu>");
+         writer.println("<vtype>"+ vel_type                 +"</vtype>");
+      }
+   }
+
+
+
+   // FIXME separate keywords into dictionary key-string (LON->"l" LAT->"b" SKYSYSTEM->"skysystem")
+   // to be part of api/QueryStringParams.java
+   String toQueryString()
+   {
+      StringBuilder sb = new StringBuilder();
+
+      sb.append("skysystem=" + skySystem);
+      sb.append("&amp;l=" + lon );
+      sb.append("&amp;b=" + lat );
+
+      switch(shape)
+      {
+         case "CIRCLE" : sb.append("&amp;r="  + radius   );
+                         break;//   writer.println("<r>"+String.valueOf(radius)+"</r>"); break;
+         case "RECT"   :
+                         sb.append("&amp;dl=" + dlon  );
+                         sb.append("&amp;db=" + dlat  );
+                         //writer.println("<dl>"+String.valueOf(dlon)+"</dl>");
+                         //writer.println("<db>"+String.valueOf(dlat)+"</db>");
+                         break;
+         default:
+                         // ERROR internal err FIXME  writer.println("<shape> unknown shape: "+ shape +" </shape>");
+      }
+
+      if(vel_valid)
+      {
+         sb.append("&amp;vl=" + vel_low);
+         sb.append("&amp;vu=" + vel_up );
+         sb.append("&amp;vt=" + vel_type );
+         //         writer.println("<vl>"   + String.valueOf(vel_low)  +"</vl>");
+         //         writer.println("<vu>"   + String.valueOf(vel_up)   +"</vu>");
+         //         writer.println("<vtype>"+ vel_type                 +"</vtype>");
+      }
+
+      return sb.toString();
+   }
+
+
+
+
+
+
+
+}
diff --git a/data-discovery/src/main/java/vlkb/common/Parser.java b/data-discovery/src/main/java/vlkb/common/Parser.java
new file mode 100644
index 0000000000000000000000000000000000000000..882d9956315573a980f0c920d3059f2729e14293
--- /dev/null
+++ b/data-discovery/src/main/java/vlkb/common/Parser.java
@@ -0,0 +1,36 @@
+
+import java.util.Map;
+
+
+class Parser
+{
+
+   static String getFirstString(Map<String, String[]> params, String key)
+   {
+      String[] values = params.get(key);
+      if (values == null) return null;
+
+      if (values.length < 1)
+         throw new IllegalArgumentException(key + " has no valid value");
+      else
+         return values[0];// FIXME if values[0] is null -> canot distinguish from key not found
+   }
+
+
+   static String[] getFirstStringArray(Map<String, String[]> params, String key, String separator, int arrayLength)
+   {
+      String array = getFirstString(params, key);
+      if (array == null) return null;
+
+      String[] stringArray = array.split(separator);
+
+      if(stringArray.length != arrayLength)
+         throw new IllegalArgumentException(
+               key + " parameter has incorrect number of elements (" 
+               + stringArray.length + " vs " + arrayLength + ") or incorrect separator used");
+
+      return stringArray;
+   }
+
+
+}
diff --git a/data-discovery/src/main/java/vlkb/common/Subsurvey.java b/data-discovery/src/main/java/vlkb/common/Subsurvey.java
new file mode 100644
index 0000000000000000000000000000000000000000..c925b0fb87da78013fdf18d14ca8287d6894831b
--- /dev/null
+++ b/data-discovery/src/main/java/vlkb/common/Subsurvey.java
@@ -0,0 +1,111 @@
+
+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
+      {
+
+         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/vlkb/common/SubsurveyId.java b/data-discovery/src/main/java/vlkb/common/SubsurveyId.java
new file mode 100644
index 0000000000000000000000000000000000000000..338e3290bceb9cdfac21e51527173520acee22cd
--- /dev/null
+++ b/data-discovery/src/main/java/vlkb/common/SubsurveyId.java
@@ -0,0 +1,57 @@
+
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+
+
+class SubsurveyId
+{
+   public String surveyName;
+   public String species;
+   public String transition;
+
+   public SubsurveyId(String surveyName, String species, String transition)
+   {
+      this.surveyName = surveyName;
+      this.species    = species;
+      this.transition = transition;
+   }
+
+
+   public SubsurveyId(Map<String, String[]> params)
+   {
+      this.surveyName = getFirstValue(params, "surveyname");
+      this.species    = getFirstValue(params, "species");
+      this.transition = getFirstValue(params, "transition");
+   }
+
+   // FIXME extend map with this and reuse in both Coord and Subsurvey
+   private String getFirstValue(Map<String, String[]> map, String key)
+   {
+      String[] value = map.get(key);
+
+      if(value == null)
+         return null;
+
+      if(value.length > 0)    // key-value present at least once: return first occurance
+         return value[0].toString();
+      else                    // length=0: no values present
+         throw new IllegalArgumentException("parameter " + key + " has no value."); 
+   }
+
+   public String toString()
+   {
+      return surveyName + " " + species + " "+ transition;
+   }
+
+
+   public void toXML(PrintWriter writer)
+   {
+      if(surveyName  != null) writer.println("<SurveyName>"+surveyName+"</SurveyName>");
+      if(species     != null) writer.println("<Species>"+species+"</Species>");
+      if(transition  != null) writer.println("<Transition>"+transition+"</Transition>");
+   }
+
+
+}
diff --git a/data-discovery/src/main/java/vlkb/common/Survey.java b/data-discovery/src/main/java/vlkb/common/Survey.java
new file mode 100644
index 0000000000000000000000000000000000000000..cf5487f209994c0c074b9a2b9b061b1c9e7cdf2a
--- /dev/null
+++ b/data-discovery/src/main/java/vlkb/common/Survey.java
@@ -0,0 +1,68 @@
+
+/* legacy from copy of DataCube.Survey
+ * FIXME equivalent to Subsurvey.java : keep only one of them */
+
+import java.io.UnsupportedEncodingException;
+
+
+/*static*/ class Survey {
+   double rf ;        // rest frequency
+   String surveyname; // surveyname
+   String species;
+   String transition;
+   String rf_unit;
+   String vel_unit;
+   String description;
+
+   Survey(){
+      this.rf          = 0.0;
+      this.surveyname  = null;
+      this.species     = null;
+      this.transition  = null;
+      this.rf_unit     = null;
+      this.vel_unit    = null;
+      this.description = null;
+   }
+
+   Survey(Survey arg_s){
+      this.rf          = arg_s.rf;
+      this.surveyname  = arg_s.surveyname;
+      this.species     = arg_s.species;
+      this.transition  = arg_s.transition;
+      this.rf_unit     = arg_s.rf_unit;
+      this.vel_unit    = arg_s.vel_unit;
+      try{
+         this.description = new String(arg_s.description.getBytes("ISO-8859-1"), "UTF-8");
+         //this.description = arg_s.description;
+      }
+      catch (UnsupportedEncodingException ex)
+      {
+         ex.printStackTrace();
+      }
+   }
+
+   String id() { return (this.surveyname + " " + this.species + " "  + this.transition); }
+   boolean matches(String id) { return id.equals(this.id()); }
+/*
+   void toXML(PrintWriter writer){
+
+      // replace with escape the XML-predefined entities:
+      // <, >, &, %
+      description = description.replace("&","&amp;");
+      description = description.replace("<","&lt;");
+      description = description.replace(">","&gt;");
+      description = description.replace("%","&#37;");
+
+      writer.println("<Description>"  + description + "</Description>");
+      writer.println("<Survey>"       + surveyname  + "</Survey>");
+      writer.println("<Species>"      + species     + "</Species>");
+      writer.println("<Transition>"   + transition  + "</Transition>");
+      writer.println("<RestFreq>");
+      writer.println("<value>"        + rf          + "</value>");
+      writer.println("<unit>"         + rf_unit     + "</unit>");
+      writer.println("</RestFreq>");
+      writer.println("<VelocityUnit>" + vel_unit    + "</VelocityUnit>");
+   }
+*/
+}
+
diff --git a/data-discovery/src/main/java/vlkb/output/Dataset.java b/data-discovery/src/main/java/vlkb/output/Dataset.java
new file mode 100644
index 0000000000000000000000000000000000000000..92f34de86ffdf6ec6a1270f8777484d65a2cf9f0
--- /dev/null
+++ b/data-discovery/src/main/java/vlkb/output/Dataset.java
@@ -0,0 +1,141 @@
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.ListIterator;
+import java.util.Iterator;
+
+
+
+class Dataset
+{
+   class Access
+   {
+      String accessFileUrl;
+      String accessCutoutUrl;
+      String accessMosaicUrl;
+   }
+
+   static class Vertices
+   {
+      final int VERT_COUNT = 4;
+      double[] lon = new double[VERT_COUNT];
+      double[] lat = new double[VERT_COUNT];
+   }
+
+
+   String subsurvey_id;
+   int overlapCodeSky;
+   int overlapCodeVel;
+   int overlapCode;
+   String dataType;
+   String publisherDid;
+   Access access;
+   Vertices vertices_deg;
+
+   Dataset()
+   {
+      access = new Access();
+      vertices_deg = new Vertices();
+   }
+
+
+   // create merged dataset if possible
+
+
+   public static boolean areDatasetsMergeable(List<Dataset> datasetList)
+   {
+      if(datasetList.size() > 1 )
+      {
+         String dataType = datasetList.get(0).dataType;
+         for(Dataset ds : datasetList)
+         {
+            if(!ds.dataType.equals(dataType)) return false;
+         }
+      }
+      return (datasetList.size() > 1) && (! hasFullOverlap(datasetList));
+   }
+
+
+   public Dataset(List<Dataset> datasetList, Inputs inputs, String mergeUrlRoot)
+   {
+      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.publisherDid = mergePublisherDids(datasetList);
+      this.dataType     = datasetList.get(0).dataType;
+
+      this.access = new Access();
+      this.access.accessFileUrl   = null;
+      this.access.accessCutoutUrl = null;
+      this.access.accessMosaicUrl = mergeUrlRoot + "?pubdid=" + publisherDid + "&amp;" +inputs.queryString;
+
+      this.vertices_deg = mergeVertices(datasetList, inputs.coord);
+   }
+
+
+   private static boolean hasFullOverlap(List<Dataset> datasetList)
+   {
+      Iterator<Dataset> it = datasetList.iterator();
+      while(it.hasNext())
+      {
+         Dataset dataset = it.next();
+         // 2: datacube inside inout, 3: inpout inside datacube, 5: two regions are identical
+         boolean fullOverlapExist =  (dataset.overlapCode == 2) || (dataset.overlapCode == 3) || (dataset.overlapCode == 5); 
+         if(fullOverlapExist) return true;
+      }
+      return false;
+   }
+
+
+   private String mergePublisherDids(List<Dataset> datasetList)
+   {
+      StringBuilder sb = new StringBuilder();
+
+      for (ListIterator<Dataset> it = datasetList.listIterator(); it.hasNext(); )
+      {
+         Dataset ds = it.next();
+         // FIXME max URL line length(?) iNet: recommendation 8000 octets or use POST
+         sb.append(";" + ds.publisherDid);
+      }
+
+      return sb.toString();
+   }
+
+
+   private Vertices mergeVertices(List<Dataset> datasetList, Coord coord)
+   {
+      // FIXME for now simply return input defined rectangle vertices
+      // which is not correct on edges of survey coverage
+
+      double ll=coord.lon, bb=coord.lat;
+      double dll=0, dbb=0; // FIXME why compilers errors (not warning): need to be inited ?
+
+      switch(coord.shape)
+      {
+         case "CIRCLE" :
+            dll = coord.radius;
+            dbb = coord.radius;
+            break;
+         case "RECT" :
+            dll = coord.dlon;
+            dbb = coord.dlat;
+            break;
+         default:
+            // FIXME internnal error
+      }
+
+      Vertices vert = new Vertices();
+
+      vert.lon[0] = ll + dll;
+      vert.lat[0] = bb + dbb;
+      vert.lon[1] = ll + dll;
+      vert.lat[1] = bb - dbb;
+      vert.lon[2] = ll - dll;
+      vert.lat[2] = bb + dbb;
+      vert.lon[3] = ll - dll;
+      vert.lat[3] = bb - dbb;
+
+      return vert;
+   }
+
+}
+
diff --git a/data-discovery/src/main/java/vlkb/output/Inputs.java b/data-discovery/src/main/java/vlkb/output/Inputs.java
new file mode 100644
index 0000000000000000000000000000000000000000..22bfd24ca4b4b9b8a8ac4228418375bd643fb582
--- /dev/null
+++ b/data-discovery/src/main/java/vlkb/output/Inputs.java
@@ -0,0 +1,39 @@
+
+
+import java.lang.StringBuilder;
+
+
+class Inputs
+{
+   Coord coord;
+   SubsurveyId subsurveyId;
+   boolean countNullValues;
+   AuthPolicy auth;
+
+   String queryString;
+
+   public Inputs(AuthPolicy auth, Coord coord, SubsurveyId subsurveyId, boolean countNullValues)
+   {
+      this.coord = coord;
+      this.subsurveyId = subsurveyId;
+      this.countNullValues = countNullValues;
+      this.auth = auth;
+
+      queryString = buildQueryString(coord, subsurveyId, countNullValues);
+   }
+
+
+   private String buildQueryString(Coord coord, SubsurveyId subsurveyId, boolean countNullValues)
+   {
+      StringBuilder queryString = new StringBuilder();
+
+      queryString.append(coord.toQueryString());
+      if(subsurveyId.surveyName != null) queryString.append("&amp;surveyname=" + subsurveyId.surveyName);
+      if(subsurveyId.species != null) queryString.append("&amp;species=" + subsurveyId.species);
+      if(subsurveyId.transition != null) queryString.append("&amp;transition=" + subsurveyId.transition);
+
+      return queryString.toString();
+   }
+
+}
+
diff --git a/data-discovery/src/main/java/vlkb/output/SearchOutputData.java b/data-discovery/src/main/java/vlkb/output/SearchOutputData.java
new file mode 100644
index 0000000000000000000000000000000000000000..1b6d9c04cc0a09549699d5663870fec83f2b29b3
--- /dev/null
+++ b/data-discovery/src/main/java/vlkb/output/SearchOutputData.java
@@ -0,0 +1,80 @@
+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;
+   Inputs inputs;
+   int datacubeCount;
+   String versionString;
+   Subsurvey[] subsurveyArr;
+
+
+   public static SearchOutputData  marshall(Dataset[] datasetArr, Inputs inputs, String mergeUrlRoot, Subsurvey[] dbSubsurveys)
+   {
+      SearchOutputData sod = new SearchOutputData();
+
+      sod.description = "Via Lactea Knowledge Base response (Search by pgSphere)";
+      sod.inputs = inputs;
+      sod.versionString = "Search (pgSphere) version " + Version.asString;
+      sod.datacubeCount = datasetArr.length;
+
+      sod.subsurveyArr = groupBySubsurveys(datasetArr, inputs, dbSubsurveys, mergeUrlRoot);
+      return sod;
+   }
+
+   private static Subsurvey[] groupBySubsurveys(Dataset[] datasetArr, Inputs inputs, Subsurvey[] dbSubsurveys, String mergeUrlRoot)
+   {
+      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( Dataset.areDatasetsMergeable(datasetList) )
+               {
+                  Dataset mergedDataset = new Dataset(datasetList, inputs, mergeUrlRoot);
+                  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( Dataset.areDatasetsMergeable(datasetList) )
+         {
+            Dataset mergedDataset = new Dataset(datasetList, inputs, mergeUrlRoot);
+            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/vlkb/output/XmlSerializer.java b/data-discovery/src/main/java/vlkb/output/XmlSerializer.java
new file mode 100644
index 0000000000000000000000000000000000000000..105e99f646f013be1d224360474cea1c4b4d52dc
--- /dev/null
+++ b/data-discovery/src/main/java/vlkb/output/XmlSerializer.java
@@ -0,0 +1,201 @@
+
+//import java.util.logging.Logger;
+import java.io.PrintWriter;
+
+
+
+public final class XmlSerializer
+{
+   //private static final Logger LOGGER = Logger.getLogger(ServletCutout.class.getName());
+
+   private XmlSerializer() {} // disables instatiation
+
+   public static void serializeToLegacyResults(PrintWriter writer, String charEncoding, 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>");
+      serialize(writer, searchOutputData.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>");
+   }
+
+   public static String serialize(Coord coord)
+   {
+      StringBuilder xml = new StringBuilder();
+      xml.append("<SkySystem>"+coord.skySystem+"</SkySystem>");
+      xml.append("<l>"+coord.lon+"</l>");
+      xml.append("<b>"+coord.lat+"</b>");
+      switch(coord.shape)
+      {
+         case "CIRCLE" :  xml.append("<r>"+String.valueOf(coord.radius)+"</r>"); break;
+         case "RECT"   :
+                          xml.append("<dl>"+String.valueOf(coord.dlon)+"</dl>");
+                          xml.append("<db>"+String.valueOf(coord.dlat)+"</db>");
+                          break;
+         default:
+                          xml.append("<shape> unknown shape: "+ coord.shape +" </shape>");
+      }
+      if(coord.vel_valid)
+      {
+         xml.append("<vl>"   + String.valueOf(coord.vel_low)  +"</vl>");
+         xml.append("<vu>"   + String.valueOf(coord.vel_up)   +"</vu>");
+         xml.append("<vtype>"+ coord.vel_type                 +"</vtype>");
+      }
+      return xml.toString();
+   }
+
+   public 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();
+   }
+
+   public 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();
+   }
+
+
+   public static void serialize(PrintWriter writer, Inputs inputs)
+   {
+      writer.println("<input>");
+      if(inputs.subsurveyId   != null) writer.println(serialize(inputs.subsurveyId));
+      if(inputs.coord       != null) writer.println(serialize(inputs.coord));
+      if(inputs.countNullValues)     writer.println("<nullvals> set </nullvals>");
+      if(inputs.auth        != null) writer.println(serialize(inputs.auth));
+      writer.println("</input>");
+   }
+
+   public 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>");
+   }
+
+   public 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 + "</URL>");
+
+      if(access.accessMosaicUrl != null)
+         xml.append("<URL type=\"mosaic\">" + access.accessMosaicUrl + "</URL>");
+
+      xml.append("</Access>");
+
+      return xml.toString();
+   }
+
+   public 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();
+   }
+
+
+   public 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();
+   }
+
+
+   public 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/vlkb/search/DbPSearch.java b/data-discovery/src/main/java/vlkb/search/DbPSearch.java
new file mode 100644
index 0000000000000000000000000000000000000000..2916825fb8d0782dc5c350f6dc20954399fc80a9
--- /dev/null
+++ b/data-discovery/src/main/java/vlkb/search/DbPSearch.java
@@ -0,0 +1,410 @@
+
+import java.util.logging.Logger;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.Driver;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.sql.SQLException;
+/* import javax.sql.*; needed if using DataSource instead of DriverManager for DB-connections */
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+import java.util.Enumeration;
+import java.util.List;
+import java.util.ArrayList;
+
+import java.lang.ClassNotFoundException;
+
+
+public class DbPSearch
+{
+   private static final Logger LOGGER = Logger.getLogger(DbPSearch.class.getName());
+   private static final SearchSettings.DBConn dbConn = SearchSettings.getInstance("discovery.properties").dbConn;
+
+   private static final String DB_DRIVER = "org.postgresql.Driver";
+
+   public Subsurvey[] getSurveyTable()
+   {
+      List<Subsurvey> survList = new ArrayList<Subsurvey>();
+
+      String theQuery = "SELECT name,species,transition,rest_frequency,restf_fits_unit,velocity_fits_unit,description FROM surveys";
+
+
+      LOGGER.info("Connecting to: " + dbConn.uri() + " with optional user/pwd: " + dbConn.userName() +" / "+ dbConn.password() );
+      try( 
+	      Connection conn = DriverManager.getConnection(dbConn.uri(), dbConn.userName(), dbConn.password());
+	      Statement  st   = conn.createStatement();
+	      ResultSet  res  = st.executeQuery(theQuery);)
+      {
+	      //ResultSet res = doQuery(theQuery);
+
+	      if(res == null)
+	      {
+		      LOGGER.info("Query yielded no resuls: " + theQuery);
+	      }
+	      else
+	      {
+		      while (res.next())
+		      {
+			      Subsurvey surv = new Subsurvey();
+			      surv.rf          = res.getDouble("rest_frequency");
+			      surv.surveyname  = res.getString("name");
+			      surv.species     = res.getString("species");
+			      surv.transition  = res.getString("transition");
+			      surv.rf_unit     = res.getString("restf_fits_unit");
+			      surv.vel_unit    = res.getString("velocity_fits_unit");
+			      surv.description = res.getString("description");
+
+			      survList.add(surv);
+		      }
+	      }
+      }
+      catch (SQLException se)
+      {
+	      logSqlExInfo(se);
+	      se.printStackTrace();
+      }
+/*      catch (ClassNotFoundException e)
+      {
+	      LOGGER.info("DB driver "+ DB_DRIVER +" not found: " + e.getMessage());
+	      e.printStackTrace();
+      }
+*/
+      return survList.toArray(new Subsurvey[0]);
+   }
+
+
+   public String[] queryOverlapingPubdid(Coord coord, SubsurveyId subsurveyId)
+   {
+	   LOGGER.info("trace");
+
+	   double lon = coord.lon;
+	   double lat = coord.lat;
+	   double radius = coord.radius;
+	   double dlon = coord.dlon;
+	   double dlat = coord.dlat;
+
+	   boolean  vel_valid   = coord.vel_valid;
+	   String   vel_type    = coord.vel_type;
+	   double   vel_low     = coord.vel_low;
+	   double   vel_up      = coord.vel_up;
+
+	   String inputRegion = null;
+
+	   if(coord.shape.equals("CIRCLE"))
+	   {
+		   inputRegion = "scircle '<(" + Double.toString(lon) + "d," + Double.toString(lat) + "d),"
+			   + Double.toString(radius) + "d>'";
+	   }
+	   else if( coord.shape.equals("RECT") )
+	   {
+		   /*Vert vert[] = toVertices(lon, lat, dlon, dlat);
+
+		     inputRegion = "spoly '{" 
+		     + "(" + Double.toString(vert[0].lon) + "d," + Double.toString(vert[0].lat) + "d)," 
+		     + "(" + Double.toString(vert[1].lon) + "d," + Double.toString(vert[1].lat) + "d)," 
+		     + "(" + Double.toString(vert[2].lon) + "d," + Double.toString(vert[2].lat) + "d)," 
+		     + "(" + Double.toString(vert[3].lon) + "d," + Double.toString(vert[3].lat) + "d)" 
+		     + "}'";
+		     */
+
+		   /* South-West and North-East corners of a box */
+		   String sw_lon = Double.toString(lon - dlon/2.0);
+		   String sw_lat = Double.toString(lat - dlat/2.0);
+		   String ne_lon = Double.toString(lon + dlon/2.0);
+		   String ne_lat = Double.toString(lat + dlat/2.0);
+
+		   inputRegion = "sbox '( ("+ sw_lon + "d, " + sw_lat + "d), (" + ne_lon +"d, " + ne_lat + "d) )'";
+	   }
+	   else
+	   {
+		   throw new IllegalArgumentException("Coord::shape was: " + coord.shape + " but valid is CIRCLE or RECT");
+	   }
+
+
+	   String theQuery ="SELECT obs_publisher_did FROM obscore WHERE (" + inputRegion + "  && polygon_region_galactic)";
+
+
+	   if(vel_valid)
+	   {
+		   String vel_no_overlap = "((em_min > " + Double.toString(vel_up) + ") OR (em_max < " + Double.toString(vel_low) + "))";
+
+		   theQuery += " AND ( (NOT " + vel_no_overlap + ") OR (em_min is null) OR (em_max is null))";
+		   /* NOTE '... OR (em_min is null)' statement causes to include 2D-continuum datasets if they overlap in sky
+		    * It is the legacy-search behaviour - however is that useful ?
+		    */
+	   }
+
+	   if(subsurveyId != null)
+	   {
+		   /* FIXME replace this implementation with exact string match once survey_id is defined / added to obs_core */
+
+		   String addSS = "";
+		   if((subsurveyId.surveyName != null) && (subsurveyId.surveyName.length() > 0))
+			   addSS += "(obs_collection LIKE '" + subsurveyId.surveyName + "%')";
+
+		   if((subsurveyId.species != null)  && (subsurveyId.species.length() > 0) )
+		   {
+			   if(addSS.length() > 0) addSS += " OR ";
+			   addSS += "(obs_collection LIKE '%" + subsurveyId.species + "%')";
+		   }
+		   if((subsurveyId.transition != null) && (subsurveyId.transition.length() > 0)  )
+		   {
+			   if(addSS.length() > 0) addSS += " OR ";
+			   addSS += "(obs_collection LIKE '%" + subsurveyId.transition + "')";
+		   };
+
+		   if(addSS.length() > 0) theQuery += " AND (" + addSS + ")";
+	   }
+
+	   //theQuery += " ORDER BY obs_collection";
+
+	   LOGGER.info(theQuery);
+
+	   List<String> pubdidList = new ArrayList<>();
+
+     	   LOGGER.info("Connecting to: " + dbConn.uri() + " with optional user/pwd: " + dbConn.userName() +" / "+ dbConn.password() );
+      	   try( 
+	      Connection conn = DriverManager.getConnection(dbConn.uri(), dbConn.userName(), dbConn.password());
+	      Statement  st   = conn.createStatement();
+	      ResultSet  res  = st.executeQuery(theQuery);)
+	   {
+		   //ResultSet res = doQuery(theQuery);
+
+		   while (res.next())
+		   {
+			   String pubdid_str = res.getString("obs_publisher_did");
+			   pubdidList.add(pubdid_str);
+		   }
+
+	   }
+	   catch (SQLException se)
+	   {
+		   logSqlExInfo(se);
+		   se.printStackTrace();
+	   }
+/*	   catch (ClassNotFoundException e)
+	   {
+		   LOGGER.info("DB driver "+ DB_DRIVER +" not found: " + e.getMessage());
+		   e.printStackTrace();
+	   }
+*/
+
+	   String[] pubdidArr = pubdidList.toArray(new String[0]);
+
+	   LOGGER.info("pubdidArr[] length: " + pubdidArr.length);
+
+	   return pubdidArr;
+   }
+
+
+
+
+   public FormatResponseFilter.ObsDataset[] queryOutputData(String[] pubdidArr, Coord coord, SubsurveyId subsurveyId)
+   {
+	   LOGGER.info("");
+	   double lon = coord.lon;
+	   double lat = coord.lat;
+	   double radius = coord.radius;
+	   double dlon = coord.dlon;
+	   double dlat = coord.dlat;
+
+	   boolean  vel_valid   = coord.vel_valid;
+	   String   vel_type    = coord.vel_type;
+	   double   vel_low     = coord.vel_low;
+	   double   vel_up      = coord.vel_up;
+
+	   String inputRegion = null;
+
+	   if(coord.shape.equals("CIRCLE"))
+	   {
+		   inputRegion = "scircle '<(" + Double.toString(lon) + "d," + Double.toString(lat) + "d),"
+			   + Double.toString(radius) + "d>'";
+	   }
+	   else if( coord.shape.equals("RECT") )
+	   {
+		   /*Vert vert[] = toVertices(lon, lat, dlon, dlat);
+
+		     inputRegion = "spoly '{" 
+		     + "(" + Double.toString(vert[0].lon) + "d," + Double.toString(vert[0].lat) + "d)," 
+		     + "(" + Double.toString(vert[1].lon) + "d," + Double.toString(vert[1].lat) + "d)," 
+		     + "(" + Double.toString(vert[2].lon) + "d," + Double.toString(vert[2].lat) + "d)," 
+		     + "(" + Double.toString(vert[3].lon) + "d," + Double.toString(vert[3].lat) + "d)" 
+		     + "}'";
+		     */
+
+		   /* South-West and North-East corners of a box */
+		   String sw_lon = Double.toString(lon - dlon/2.0);
+		   String sw_lat = Double.toString(lat - dlat/2.0);
+		   String ne_lon = Double.toString(lon + dlon/2.0);
+		   String ne_lat = Double.toString(lat + dlat/2.0);
+
+		   inputRegion = "sbox '( ("+ sw_lon + "d, " + sw_lat + "d), (" + ne_lon +"d, " + ne_lat + "d) )'";
+	   }
+	   else
+	   {
+		   throw new IllegalArgumentException("Coord::shape was: " + coord.shape + " but valid is CIRCLE or RECT");
+	   }
+
+	   String commaSepPubdids  = String.join("\',\'", pubdidArr);
+	   String theQuery ="SELECT dataproduct_type,obs_publisher_did,obs_collection,polygon_region_galactic,access_url,em_min,em_max," 
+		   + inputRegion + " <@ polygon_region_galactic AS inputInsideDb, " 
+		   + inputRegion + " @> polygon_region_galactic AS dbInsideInput FROM obscore WHERE (obs_publisher_did IN (\'"+commaSepPubdids+"\'))";
+
+
+	   theQuery += " ORDER BY obs_collection";
+
+	   //LOGGER.info(theQuery);
+
+	   List<FormatResponseFilter.ObsDataset> obsDatasetList = new ArrayList<>();
+
+	   LOGGER.info("Connecting to: " + dbConn.uri() + " with optional user/pwd: " + dbConn.userName() +" / "+ dbConn.password() );
+      	   try( 
+	      Connection conn = DriverManager.getConnection(dbConn.uri(), dbConn.userName(), dbConn.password());
+	      Statement  st   = conn.createStatement();
+	      ResultSet  res  = st.executeQuery(theQuery);)
+	   {
+		   //ResultSet res = doQuery(theQuery);
+
+		   while (res.next())
+		   {
+			   FormatResponseFilter.ObsDataset obsDataset = new FormatResponseFilter.ObsDataset();
+			   obsDataset.data_type     = res.getString("dataproduct_type");
+			   obsDataset.pubdid_str    = res.getString("obs_publisher_did");
+			   obsDataset.subsurvey_id  = res.getString("obs_collection");
+			   obsDataset.vertices_str  = res.getString("polygon_region_galactic");
+			   obsDataset.access_url    = res.getString("access_url");
+			   obsDataset.inputInsideDb = res.getBoolean("inputInsideDb");
+			   obsDataset.dbInsideInput = res.getBoolean("dbInsideInput");
+
+			   obsDataset.em_min = res.getDouble("em_min");
+			   boolean em_min_valid = !res.wasNull();
+			   obsDataset.em_max = res.getDouble("em_max");
+			   boolean em_max_valid = !res.wasNull();
+			   obsDataset.em_valid = em_min_valid && em_max_valid;;
+
+			   obsDatasetList.add(obsDataset);
+		   }
+
+		   LOGGER.info("From DB collected # of ObsDataset : " + obsDatasetList.size());
+	   }
+	   catch (SQLException se)
+	   {
+		   logSqlExInfo(se);
+		   se.printStackTrace();
+	   }
+/*	   catch (ClassNotFoundException e)
+	   {
+		   LOGGER.info("DB driver "+ DB_DRIVER +" not found: " + e.getMessage());
+		   e.printStackTrace();
+	   }
+*/
+
+	   FormatResponseFilter.ObsDataset[] cubes = obsDatasetList.toArray(new FormatResponseFilter.ObsDataset[0]);
+
+	   return cubes;
+   }
+/*
+   public static void loadDriver()// throws ClassNotFoundException
+   {
+	   /* https://docs.oracle.com/javase/tutorial/jdbc/basics/connecting.html :
+	    * Any JDBC 4.0 drivers that are found in your class path are automatically loaded.
+	    * (However, you must manually load any drivers prior to JDBC 4.0 with the method
+	    * Class.forName.)
+	    * /
+	   /* OR
+	    * DriverManager.registerDriver(new org.postgresql.Driver());
+	    * LOGGER.info(getClasspathString());
+	    * LOGGER.info(getRegisteredDriverList());
+	    *i /
+	   try
+	   {
+//   Class.forName(DB_DRIVER);
+	   }
+	   catch (ClassNotFoundException e)
+	   {
+		   LOGGER.info("DB driver "+ DB_DRIVER +" not found: " + e.getMessage());
+		   e.printStackTrace();
+	   }
+   }
+*/
+   /*
+      private ResultSet doQuery(String theQuery) throws SQLException, ClassNotFoundException
+      {
+   /* https://docs.oracle.com/javase/tutorial/jdbc/basics/connecting.html :
+    * Any JDBC 4.0 drivers that are found in your class path are automatically loaded.
+    * (However, you must manually load any drivers prior to JDBC 4.0 with the method
+    * Class.forName.)
+    * /
+//    Class.forName(DB_DRIVER);
+   /* OR
+    * DriverManager.registerDriver(new org.postgresql.Driver());
+    * LOGGER.info(getClasspathString());
+    * LOGGER.info(getRegisteredDriverList());
+    * /
+
+    LOGGER.info("Connecting to: " + dbConn.uri() + " with optional user/pwd: " + dbConn.userName() +" / "+ dbConn.password() );
+
+    Connection conn = DriverManager.getConnection(dbConn.uri(), dbConn.userName(), dbConn.password());
+
+    Statement  st   = conn.createStatement();
+
+    ResultSet  res  = st.executeQuery(theQuery);
+
+    return res;
+      }
+      */
+
+
+
+   private void logSqlExInfo(SQLException se)
+   {
+	   LOGGER.info("SQLState : " + se.getSQLState());
+	   LOGGER.info("ErrorCode: " + se.getErrorCode());
+	   LOGGER.info("Message  : " + se.getMessage());
+	   Throwable t = se.getCause();
+	   while(t != null) {
+		   LOGGER.info("Cause: " + t);
+		   t = t.getCause();
+	   }
+   }
+
+
+
+   private String getClasspathString()
+   {
+	   StringBuffer classpath = new StringBuffer("getClasspathString:\r\n");
+	   ClassLoader applicationClassLoader = this.getClass().getClassLoader();
+	   if (applicationClassLoader == null) {
+		   applicationClassLoader = ClassLoader.getSystemClassLoader();
+	   }
+	   URL[] urls = ((URLClassLoader)applicationClassLoader).getURLs();
+	   for(int i=0; i < urls.length; i++) {
+		   classpath.append(urls[i].getFile()).append("\r\n");
+	   }
+
+	   return classpath.toString();
+   }
+
+
+   /* Returns the list of JDBC Drivers loaded by the caller's class loader */
+   private String getRegisteredDriverList()
+   {
+	   StringBuffer drvList = new StringBuffer("getRegisteredDriverList:\r\n");
+	   for (Enumeration e = DriverManager.getDrivers();
+			   e.hasMoreElements(); )
+	   {
+		   Driver d = (Driver) e.nextElement();
+		   String driverClass = d.getClass().getName();
+		   drvList.append(driverClass).append("\r\n");	
+	   }
+	   return drvList.toString();
+   }
+
+
+
+}
diff --git a/data-discovery/src/main/java/vlkb/vosi/VlkbServletFile.java b/data-discovery/src/main/java/vlkb/vosi/VlkbServletFile.java
new file mode 100644
index 0000000000000000000000000000000000000000..74f531f56a32a8d3103d034f7f180d1b6c269aba
--- /dev/null
+++ b/data-discovery/src/main/java/vlkb/vosi/VlkbServletFile.java
@@ -0,0 +1,150 @@
+//
+// return content of xml 
+// (used for VOSI capabilityVOSI and availability.xml)
+//
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.File;
+import java.io.OutputStream;
+import java.util.Enumeration;
+import java.util.*; // ArrayList<String>
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.ServletOutputStream; // for SOOA
+
+// from vlkb_mergefiles.java - dir & file handling
+import java.io.*;
+
+import java.nio.file.*;
+import static java.nio.file.StandardCopyOption.*;
+
+
+// serve VOSI resources from xml files (for now implemented as strings, not files FIXME)
+
+public class VlkbServletFile
+    extends javax.servlet.http.HttpServlet
+{
+    // for logs and debug
+    String className = this.getClass().getSimpleName();
+
+// VOSI
+// String accessURL = null; // FIXME now read from MERGEURL later introduce own param
+// String funcName = "vlkb_cutout"; // FIXME read from config file
+
+	private static final String availStr = 
+		  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+		+ "<vosi:availability  "
+		+ " xmlns:vosi=\"http://www.ivoa.net/xml/VOSIAvailability/v1.0\">"
+		+ " <vosi:available>true</vosi:available>"
+		+ " <vosi:note>service is accepting queries</vosi:note>"
+		+ "</vosi:availability>";
+
+	private String capsStr = null;
+
+
+	protected void SetCapsStr(String URL, String funcName)
+	{
+		if(URL != null)
+		{
+		
+            String accessURL = stripTrailingSlash(URL);
+
+    capsStr =
+		  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+
+		+ "<vosi:capabilities "
+		+    "xmlns:vosi=\"http://www.ivoa.net/xml/VOSICapabilities/v1.0\" "
+		+    "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
+		+    "xmlns:vod=\"http://www.ivoa.net/xml/VODataService/v1.1\">"
+
+		+ " <capability standardID=\"ivo://ivoa.net/std/VOSI#capabilities\">"
+		+ "   <interface xsi:type=\"vod:ParamHTTP\" version=\"1.0\">"
+		+ "     <accessURL use=\"full\">"
+		+          accessURL + "/capabilities"
+		+ "     </accessURL>"
+		+ "   </interface>"
+		+ " </capability>"
+
+		+ " <capability standardID=\"ivo://ivoa.net/std/VOSI#availability\">"
+		+ "   <interface xsi:type=\"vod:ParamHTTP\" version=\"1.0\">"
+		+ "     <accessURL use=\"full\">"
+		+          accessURL + "/availability"
+		+ "     </accessURL>"
+		+ "   </interface>"
+		+ " </capability>"
+
+		+ " <capability standardID=\"ivo://ivoa.net/std/SODA#sync-1.0\">"
+		+ "   <interface xsi:type=\"vod:ParamHTTP\" role=\"std\" version=\"1.0\">"
+		+ "     <accessURL use=\"full\">"
+		+          accessURL + "/" + funcName
+		+ "     </accessURL>"
+		+ "   </interface>"
+		+ " </capability>"
+
+		+ " <capability standardID=\"ivo://ivoa.net/std/SODA#async-1.0\">"
+		+ "   <interface xsi:type=\"vod:ParamHTTP\" role=\"std\" version=\"1.0\">"
+		+ "     <accessURL use=\"full\">"
+		+          accessURL + "/" + funcName + "_uws/soda_cuts"
+		+ "     </accessURL>"
+		+ "   </interface>"
+		+ " </capability>"
+
+	+ "</vosi:capabilities>";
+		}
+	}
+
+
+	String stripTrailingSlash(String path)
+	{
+       		if (path.endsWith("/"))
+           		return path.substring(0,path.length()-1);
+       		else
+           		return path;
+    	}
+
+
+    protected void doGet(HttpServletRequest request,
+                         HttpServletResponse response)
+        throws ServletException, IOException {
+
+            doPost(request, response);
+        }
+
+
+
+    protected void doPost(HttpServletRequest request,
+                          HttpServletResponse response)
+        throws ServletException, IOException
+	{
+		StringBuffer requestURL = request.getRequestURL();
+	
+   	System.out.println(className + " vlkb req from: " + request.getRemoteAddr()
+                               + " doGet: " + requestURL.toString());
+
+		PrintWriter writer = response.getWriter();
+		response.setContentType("text/xml");
+	
+		if(-1 != requestURL.lastIndexOf("/capabilities"))
+		{
+            String fullURL = request.getRequestURL().toString();
+            String baseURL = fullURL.substring(0,requestURL.lastIndexOf("/"));
+
+            SetCapsStr(baseURL, "soda");
+			writer.println(capsStr);	
+
+		}
+		else if(-1 != requestURL.lastIndexOf("/availability"))
+		{
+			writer.println(availStr);	
+		}
+		// error FIXME what to do if none of above given ? e.g. misconfigured web.xml
+		
+		writer.close();
+		return;
+        }
+}
+
diff --git a/data-discovery/src/main/java/vlkb/webapi/AuthorizationResponseFilter.java b/data-discovery/src/main/java/vlkb/webapi/AuthorizationResponseFilter.java
new file mode 100644
index 0000000000000000000000000000000000000000..78f485105b7fb18e579ad5be1cfd2455eacbd998
--- /dev/null
+++ b/data-discovery/src/main/java/vlkb/webapi/AuthorizationResponseFilter.java
@@ -0,0 +1,83 @@
+
+import java.util.logging.Logger;
+import java.util.List;
+import java.io.*;
+ 
+import javax.servlet.*;
+//import javax.servlet.annotation.WebFilter;
+import javax.servlet.http.HttpServletResponse;
+
+import javax.servlet.http.*;
+
+
+
+
+
+//@WebFilter("/*")
+public class AuthorizationResponseFilter implements Filter
+{
+   private static final Logger LOGGER = Logger.getLogger("ResponseFilter");
+   private static final AuthorizationResponseSettings settings = AuthorizationResponseSettings.getInstance("authpolicy.properties");
+
+
+   @Override
+   public void init(FilterConfig filterConfig) throws ServletException
+   {
+      LOGGER.info("trace");
+   }
+
+
+
+
+
+
+
+   @Override
+   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+      throws IOException, ServletException
+   {
+      LOGGER.info("trace");
+
+      chain.doFilter(request, response);
+
+      if(response instanceof PubdidResponseWrapper)
+      {
+         PubdidResponseWrapper pw = (PubdidResponseWrapper) response;
+         LOGGER.info("after doFilter : " + pw.getPubdidArr().length);
+
+         //if (pubdidWrapper.getContentType().contains("text/plain"))
+         {
+            String[] pubdidArr = pw.getPubdidArr();
+
+            AuthPolicy auth = null;
+            try
+            {
+               HttpServletRequest req = (HttpServletRequest) request;
+               auth = new AuthPolicy(req.getUserPrincipal());
+            }
+            catch(IllegalArgumentException ex)
+            {
+               throw new IllegalArgumentException("Authorization : UserPrincipal is not of expected type");
+            }
+
+            pw.setAuth(auth);
+            String[] authorizedPubdidArr = auth.filterAuthorized(pubdidArr,
+                  settings.dbConn.uri(), settings.dbConn.userName(), settings.dbConn.password());
+
+            pw.setPubdidArr(authorizedPubdidArr);
+         }
+      }
+      else
+      {
+         throw new IllegalStateException("Response is not of expected type. Cannot performs authorization check for pubdid array.");
+      }
+   }
+
+   @Override
+   public void destroy()
+   {
+      LOGGER.info("");
+   }
+
+}
+
diff --git a/data-discovery/src/main/java/vlkb/webapi/AuthorizationResponseSettings.java b/data-discovery/src/main/java/vlkb/webapi/AuthorizationResponseSettings.java
new file mode 100644
index 0000000000000000000000000000000000000000..779b10e24aafba73bc73702e31c58d918b8c67c1
--- /dev/null
+++ b/data-discovery/src/main/java/vlkb/webapi/AuthorizationResponseSettings.java
@@ -0,0 +1,122 @@
+
+import java.util.logging.Logger;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+import java.io.PrintWriter;
+
+
+class AuthorizationResponseSettings
+{
+   private static final Logger LOGGER = Logger.getLogger("AuthorizationResponseSettings");
+
+
+   public static class DBConn
+   {
+      private String uri;
+      private String schema;
+      private String user_name;
+      private String password;
+
+      public String uri() {return uri;}
+      public String schema() {return schema;}
+      public String userName() {return user_name;}
+      public String password() {return password;}
+
+      public String toString()
+      {
+         return uri + "  schema[" + schema +  "] " + user_name + " / " + password;
+      }
+   }
+
+
+   public static class ServiceUrls
+   {
+      private String cutoutUrl;
+      private String mergeUrl;
+      private String surveysAbsPathname;
+
+      public boolean cutoutUrlIsSet() { return (cutoutUrl != null) && cutoutUrl.trim().isEmpty(); }
+      public boolean mergeUrlIsSet()  { return (mergeUrl != null) && mergeUrl.trim().isEmpty(); }
+      public boolean surveysAbsPathnameIsSet()
+       { return (surveysAbsPathname != null) && surveysAbsPathname.trim().isEmpty(); }
+
+      public String cutoutUrl() {return cutoutUrl;}
+      public String mergeUrl()  {return mergeUrl;}
+      public String surveysAbsPathname()  {return surveysAbsPathname;}
+
+      public String toString()
+      {
+         return cutoutUrl + "   "  + mergeUrl + "   " + surveysAbsPathname;
+      }
+   }
+
+
+   public DBConn     dbConn;
+   public ServiceUrls serviceUrls;
+
+
+   // will not start without config-file; no reasonable code-defaults can be invented
+   public static AuthorizationResponseSettings getInstance(String settingsFileName)
+   {
+      try
+      {
+         InputStream ins = AuthorizationResponseSettings.class.getClassLoader().getResourceAsStream(settingsFileName);
+
+         if (ins != null)
+         {
+            Properties properties = new Properties();
+            properties.load(ins);
+
+            DBConn      dbConn      = loadDBConn(properties);
+            ServiceUrls serviceUrls = loadServiceUrls(properties);
+
+            return new AuthorizationResponseSettings(dbConn, serviceUrls);
+         }
+         else
+         {
+            throw new IllegalStateException(settingsFileName + " not found in classpath");
+         }
+
+      }
+      catch(IOException ex)
+      {
+         throw new IllegalStateException("Error while loading " + settingsFileName + " file", ex);
+      }
+   }
+
+
+
+
+   private AuthorizationResponseSettings(DBConn dbConn, ServiceUrls serviceUrls)
+   {
+      this.dbConn      = dbConn;
+      this.serviceUrls = serviceUrls;
+   }
+
+
+   private static DBConn loadDBConn(Properties properties)
+   {
+      DBConn dbConn = new AuthorizationResponseSettings.DBConn();
+      dbConn.uri       = properties.getProperty("db_uri","jdbc:postgresql://localhost:5432/vialactea").strip();
+      dbConn.schema    = properties.getProperty("db_schema","datasets").strip();
+      dbConn.user_name = properties.getProperty("db_user_name","").strip();
+      dbConn.password  = properties.getProperty("db_password","").strip();
+
+      return dbConn;
+   }
+
+
+   private static ServiceUrls loadServiceUrls(Properties properties)
+   {
+      ServiceUrls serviceUrls = new ServiceUrls();
+      serviceUrls.cutoutUrl = properties.getProperty("cutout_url","").strip();
+      serviceUrls.mergeUrl  = properties.getProperty("merge_url","").strip();
+      serviceUrls.surveysAbsPathname = properties.getProperty("surveys_metadata_abs_pathname","/srv/surveys/surveys_metadata.csv").strip();
+      return serviceUrls;
+   }
+
+
+}
+
diff --git a/data-discovery/src/main/java/vlkb/webapi/FormatResponseFilter.java b/data-discovery/src/main/java/vlkb/webapi/FormatResponseFilter.java
new file mode 100644
index 0000000000000000000000000000000000000000..49e1955df5971147606c33ef087833112cde0031
--- /dev/null
+++ b/data-discovery/src/main/java/vlkb/webapi/FormatResponseFilter.java
@@ -0,0 +1,270 @@
+
+import java.util.logging.Logger;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.io.*;
+ 
+
+import javax.servlet.*;
+import javax.servlet.http.HttpServletResponse;
+
+import javax.servlet.http.*;
+ 
+import java.nio.charset.Charset;
+
+
+class PubdidResponseWrapper extends HttpServletResponseWrapper
+{
+   private SearchDescription search;
+
+   public PubdidResponseWrapper(HttpServletResponse response)
+   {
+      super(response);
+      search = null;
+   }
+
+   public void set(SearchDescription search) { this.search = search;}
+   public SearchDescription get() { return this.search;}
+
+   public void setPubdidArr(String[] pubdidArr) { this.search.pubdidArr = pubdidArr; }
+   public String[] getPubdidArr() { return this.search.pubdidArr; }
+
+
+   public void setAuth(AuthPolicy auth)
+   {
+      assert (this.search.inputs.auth != null);
+      this.search.inputs.auth = auth;
+   }
+}
+
+
+
+
+
+public class FormatResponseFilter implements Filter
+{
+   private static final Logger LOGGER = Logger.getLogger("FormatResponseFilter");
+   private static final FormatResponseSettings settings = FormatResponseSettings.getInstance("formatresponsefilter.properties");
+
+   final String RESPONSE_ENCODING = "UTF-8";
+   protected Subsurvey[] dbSubsurveyArr  = null;
+
+
+   @Override
+   public void init(FilterConfig filterConfig) throws ServletException
+   {
+      LOGGER.info("trace");
+
+//      DbPSearch.loadDriver();
+/*
+      FIXME DbPSearch is using Settings.DBConn from SearchServlet -> discovery.properties
+
+      /* load Surveys table * /
+      DbPSearch vlkbSql;
+      synchronized(DbPSearch.class)
+      {
+         vlkbSql = new DbPSearch();
+      }
+
+      dbSubsurveyArr = vlkbSql.getSurveyTable();
+*/
+      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());
+   }
+
+
+
+   @Override
+   public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
+   {
+      LOGGER.info("trace");
+      LOGGER.info("REQUEST START =============================================================================================");
+
+      PubdidResponseWrapper pubdidWrapper = new PubdidResponseWrapper((HttpServletResponse) response);
+
+      chain.doFilter(request, pubdidWrapper);
+
+      long startTime_msec = System.currentTimeMillis();
+
+      PrintWriter responseWriter = ((HttpServletResponse)response).getWriter();
+
+      if (true)
+         //if (pubdidWrapper.getContentType().contains("text/plain"))
+      {
+
+         SearchDescription search = pubdidWrapper.get();
+
+         Dataset[] datasetArr = getOutputData(
+               search.pubdidArr,
+               search.inputs.coord,
+               search.inputs.subsurveyId,
+               search.inputs.queryString,
+               settings.serviceUrls.cutoutUrl());
+
+         SearchOutputData searchOutputData = SearchOutputData.marshall(
+               datasetArr,
+               search.inputs,
+               settings.serviceUrls.mergeUrl(),
+               dbSubsurveyArr);
+
+         response.setContentType("application/xml");
+         response.setCharacterEncoding(RESPONSE_ENCODING);
+
+         boolean showDuration = true;
+         XmlSerializer.serializeToLegacyResults(responseWriter, RESPONSE_ENCODING, searchOutputData,showDuration,startTime_msec);
+
+         responseWriter.close();
+      }
+      LOGGER.info("REQUEST END   =============================================================================================");
+   }
+
+
+
+   @Override
+   public void destroy()
+   {
+      LOGGER.info("trace");
+   }
+
+
+   ///////////////////////////////////////////////////////////////////
+   // collect output data from DB
+
+
+   /* FIXME type needed in DbPSearch */
+   static class ObsDataset
+   {
+      String data_type;
+      String pubdid_str;
+      String subsurvey_id;
+      String vertices_str;
+      String access_url;
+      boolean inputInsideDb;
+      boolean dbInsideInput;
+
+      boolean em_valid;
+      double em_min;
+      double em_max;
+   }
+
+
+
+   private Dataset[] getOutputData(String[] pubdidArr, Coord coord,SubsurveyId subsurveyId, String queryString, String fitsRemotePath)
+   {
+      LOGGER.info("trace");
+
+      DbPSearch dbps;
+      synchronized(DbPSearch.class)
+      {
+         dbps = new DbPSearch();
+      }
+
+      FormatResponseFilter.ObsDataset[] obsDatasetArr = dbps.queryOutputData(pubdidArr, coord, subsurveyId);
+
+      return convert(obsDatasetArr, coord, queryString, fitsRemotePath);
+   }
+
+
+
+
+
+   private Dataset[] convert(FormatResponseFilter.ObsDataset[] obsDatasetArr, Coord coord, String queryString, String fitsRemotePath)
+   {
+      List<Dataset> datasetList  = new ArrayList<Dataset>();
+      for(FormatResponseFilter.ObsDataset obsDataset : obsDatasetArr)
+      {
+         Dataset dataset = new Dataset();
+
+         dataset.subsurvey_id   = obsDataset.subsurvey_id;
+         dataset.overlapCodeSky = convertToOverlapCodeSky(obsDataset.inputInsideDb, obsDataset.dbInsideInput);
+         dataset.overlapCodeVel = convertToOverlapCodeVel(coord, obsDataset.em_valid, obsDataset.em_min, obsDataset.em_max);
+         dataset.overlapCode    = convertToOverlapCode(dataset.overlapCodeSky, dataset.overlapCodeVel);
+         dataset.dataType       = obsDataset.data_type;
+         dataset.publisherDid   = obsDataset.pubdid_str;
+
+         dataset.access.accessFileUrl   = obsDataset.access_url;
+         dataset.access.accessCutoutUrl = fitsRemotePath + "?pubdid=" + dataset.publisherDid + "&amp;" + queryString;
+         dataset.access.accessMosaicUrl  = null;
+
+         dataset.vertices_deg = convertToVertices(obsDataset.vertices_str);
+
+         datasetList.add(dataset);
+      }
+      return datasetList.toArray(new Dataset[0]);
+   }
+
+
+   private int convertToOverlapCodeSky(boolean inpInDb, boolean dbInInp)
+   {
+      if(!inpInDb && !dbInInp) return 4; // parial overlap
+      else if( inpInDb && !dbInInp) return 3; // input region completely inside fits-datacube
+      else if(!inpInDb &&  dbInInp) return 2; // datacube completely inside input-region
+      else return 5; // exact match: both inpInDb dbInInp are true
+   }
+
+
+   private int convertToOverlapCodeVel(Coord coord, boolean v_valid, double v_min, double v_max)
+   {
+      if(coord.vel_valid && v_valid)
+      {
+         if(coord.vel_type.equals("1"))
+         {
+            // FIXME assert coord: vel_min <= vel_max
+            // FIXME assert cube:  v_min   <= v_max
+
+            boolean dbInInp = (coord.vel_low <= v_min) && (v_min <= coord.vel_up)
+               && (coord.vel_low <= v_max) && (v_max <= coord.vel_up);
+
+            boolean inpInDb = (v_min <= coord.vel_low) && (coord.vel_low <= v_max)
+               && (v_min <= coord.vel_up ) && (coord.vel_up  <= v_max);
+
+            return convertToOverlapCodeSky(inpInDb, dbInInp);
+
+         }
+         else ;// FIXME other v_type NotImplemented yet
+      }
+
+      return -1; // FIXME use enums; meaning: overlap code in velocity not applicable --> dont print in XML
+   }
+
+
+
+   private int convertToOverlapCode(int ovcSky, int ovcVel)
+   {
+      if(ovcVel == -1) return ovcSky; // 2D images
+      else if(ovcSky == ovcVel) return ovcSky;
+      else return 4;
+   }
+
+
+   private Dataset.Vertices convertToVertices(String polygon)
+   {
+      final double RAD2DEG = 180.0 / Math.PI;
+
+      Dataset.Vertices vert = new Dataset.Vertices();
+
+      polygon = polygon.replace("("," ");
+      polygon = polygon.replace(")"," ");
+      polygon = polygon.replace("{"," ");
+      polygon = polygon.replace("}"," ");
+
+      String[] verts = polygon.split(",");
+
+      vert.lon[0] = RAD2DEG * Double.parseDouble(verts[0]);
+      vert.lat[0] = RAD2DEG * Double.parseDouble(verts[1]);
+      vert.lon[1] = RAD2DEG * Double.parseDouble(verts[2]);
+      vert.lat[1] = RAD2DEG * Double.parseDouble(verts[3]);
+      vert.lon[2] = RAD2DEG * Double.parseDouble(verts[4]);
+      vert.lat[2] = RAD2DEG * Double.parseDouble(verts[5]);
+      vert.lon[3] = RAD2DEG * Double.parseDouble(verts[6]);
+      vert.lat[3] = RAD2DEG * Double.parseDouble(verts[7]);
+
+      return vert;
+   }
+
+
+}
diff --git a/data-discovery/src/main/java/vlkb/webapi/FormatResponseSettings.java b/data-discovery/src/main/java/vlkb/webapi/FormatResponseSettings.java
new file mode 100644
index 0000000000000000000000000000000000000000..9e22cb5ab853ac6301ec81390761bf0793e012be
--- /dev/null
+++ b/data-discovery/src/main/java/vlkb/webapi/FormatResponseSettings.java
@@ -0,0 +1,122 @@
+
+import java.util.logging.Logger;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+import java.io.PrintWriter;
+
+
+class FormatResponseSettings
+{
+   private static final Logger LOGGER = Logger.getLogger("FormatResponseSettings");
+
+
+   public static class DBConn
+   {
+      private String uri;
+      private String schema;
+      private String user_name;
+      private String password;
+
+      public String uri() {return uri;}
+      public String schema() {return schema;}
+      public String userName() {return user_name;}
+      public String password() {return password;}
+
+      public String toString()
+      {
+         return uri + "  schema[" + schema +  "] " + user_name + " / " + password;
+      }
+   }
+
+
+   public static class ServiceUrls
+   {
+      private String cutoutUrl;
+      private String mergeUrl;
+      private String surveysAbsPathname;
+
+      public boolean cutoutUrlIsSet() { return (cutoutUrl != null) && cutoutUrl.trim().isEmpty(); }
+      public boolean mergeUrlIsSet()  { return (mergeUrl != null) && mergeUrl.trim().isEmpty(); }
+      public boolean surveysAbsPathnameIsSet()
+       { return (surveysAbsPathname != null) && surveysAbsPathname.trim().isEmpty(); }
+
+      public String cutoutUrl() {return cutoutUrl;}
+      public String mergeUrl()  {return mergeUrl;}
+      public String surveysAbsPathname()  {return surveysAbsPathname;}
+
+      public String toString()
+      {
+         return cutoutUrl + "   "  + mergeUrl + "   " + surveysAbsPathname;
+      }
+   }
+
+
+   public DBConn     dbConn;
+   public ServiceUrls serviceUrls;
+
+
+   // will not start without config-file; no reasonable code-defaults can be invented
+   public static FormatResponseSettings getInstance(String settingsFileName)
+   {
+      try
+      {
+         InputStream ins = FormatResponseSettings.class.getClassLoader().getResourceAsStream(settingsFileName);
+
+         if (ins != null)
+         {
+            Properties properties = new Properties();
+            properties.load(ins);
+
+            DBConn      dbConn      = loadDBConn(properties);
+            ServiceUrls serviceUrls = loadServiceUrls(properties);
+
+            return new FormatResponseSettings(dbConn, serviceUrls);
+         }
+         else
+         {
+            throw new IllegalStateException(settingsFileName + " not found in classpath");
+         }
+
+      }
+      catch(IOException ex)
+      {
+         throw new IllegalStateException("Error while loading " + settingsFileName + " file", ex);
+      }
+   }
+
+
+
+
+   private FormatResponseSettings(DBConn dbConn, ServiceUrls serviceUrls)
+   {
+      this.dbConn      = dbConn;
+      this.serviceUrls = serviceUrls;
+   }
+
+
+   private static DBConn loadDBConn(Properties properties)
+   {
+      DBConn dbConn = new FormatResponseSettings.DBConn();
+      dbConn.uri       = properties.getProperty("db_uri","jdbc:postgresql://localhost:5432/vialactea").strip();
+      dbConn.schema    = properties.getProperty("db_schema","datasets").strip();
+      dbConn.user_name = properties.getProperty("db_user_name","").strip();
+      dbConn.password  = properties.getProperty("db_password","").strip();
+
+      return dbConn;
+   }
+
+
+   private static ServiceUrls loadServiceUrls(Properties properties)
+   {
+      ServiceUrls serviceUrls = new ServiceUrls();
+      serviceUrls.cutoutUrl = properties.getProperty("cutout_url","").strip();
+      serviceUrls.mergeUrl  = properties.getProperty("merge_url","").strip();
+      serviceUrls.surveysAbsPathname = properties.getProperty("surveys_metadata_abs_pathname","/srv/surveys/surveys_metadata.csv").strip();
+      return serviceUrls;
+   }
+
+
+}
+
diff --git a/data-discovery/src/main/java/vlkb/webapi/MonitorFilter.java b/data-discovery/src/main/java/vlkb/webapi/MonitorFilter.java
new file mode 100644
index 0000000000000000000000000000000000000000..721e4de77a8f4cd3fab06e18c49044f763a6d571
--- /dev/null
+++ b/data-discovery/src/main/java/vlkb/webapi/MonitorFilter.java
@@ -0,0 +1,50 @@
+
+//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/vlkb/webapi/SearchDescription.java b/data-discovery/src/main/java/vlkb/webapi/SearchDescription.java
new file mode 100644
index 0000000000000000000000000000000000000000..5cb4fa242bb81d8500cc6dd882105cc992df4e35
--- /dev/null
+++ b/data-discovery/src/main/java/vlkb/webapi/SearchDescription.java
@@ -0,0 +1,10 @@
+
+
+
+
+
+class SearchDescription
+{
+   String[] pubdidArr;
+   Inputs inputs;
+}
diff --git a/data-discovery/src/main/java/vlkb/webapi/SearchServlet.java b/data-discovery/src/main/java/vlkb/webapi/SearchServlet.java
new file mode 100644
index 0000000000000000000000000000000000000000..b880972bdd85b2c3abb80a8132f705d331194a90
--- /dev/null
+++ b/data-discovery/src/main/java/vlkb/webapi/SearchServlet.java
@@ -0,0 +1,132 @@
+
+import java.util.logging.Logger;
+import java.util.Map;
+import java.util.List;
+import java.util.ArrayList;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.charset.Charset;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+
+import java.security.Principal;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+public class SearchServlet extends javax.servlet.http.HttpServlet
+{
+   private static final Logger         LOGGER   = Logger.getLogger("PSearch");
+   private static final SearchSettings settings = SearchSettings.getInstance("discovery.properties");
+
+
+   public void init() throws ServletException
+   {
+      super.init();
+
+      LOGGER.info("URLs : " + settings.serviceUrls.toString());
+      LOGGER.info("DB   : " + settings.dbConn.toString());
+   }
+
+
+   protected void doGet(HttpServletRequest request, HttpServletResponse response)
+      throws ServletException, IOException, UnsupportedEncodingException
+   {
+      LOGGER.info("trace");
+
+      long startTime_msec = System.currentTimeMillis();
+
+      legacyLogEntry(request);
+
+      Map<String, String[]> params = request.getParameterMap();
+
+      try
+      {
+         Coord       coord           = new Coord(params);
+         SubsurveyId subsurveyId     = new SubsurveyId(params);
+
+         Inputs inputs = new Inputs(/*auth*/null, coord, subsurveyId, false);
+
+         /* query Obscore table */
+
+         DbPSearch dbps;
+         synchronized(DbPSearch.class)
+         {
+            dbps = new DbPSearch();
+         }
+         String[] pubdidArr = dbps.queryOverlapingPubdid(coord, subsurveyId);
+
+
+
+         final String RESPONSE_ENCODING = "UTF-8";
+
+         /* if filters installed response will be wrapped */
+
+         if(response instanceof PubdidResponseWrapper)
+         {
+            LOGGER.info("response-type is PubdidResponseWrapper");
+
+            response.setContentType("text/plain");
+            response.setCharacterEncoding(RESPONSE_ENCODING);
+
+            /* collect all search description and set to wrapped response */
+
+            SearchDescription search = new SearchDescription();
+            search.pubdidArr = pubdidArr;
+            search.inputs = inputs;
+
+            PubdidResponseWrapper pubdidWrapper = (PubdidResponseWrapper) response;
+            pubdidWrapper.set(search);
+         }
+         else
+         {
+            LOGGER.info("response-type is HttpServletResponse");
+
+            response.setContentType("text/plain");
+            response.setCharacterEncoding(RESPONSE_ENCODING);
+            PrintWriter writer = response.getWriter();
+
+            for(String pubdid : pubdidArr)
+            {
+               writer.println(pubdid);
+            }
+            writer.close();
+         }
+      }
+      catch (IllegalArgumentException illArg)
+      {
+         response.sendError(HttpServletResponse.SC_BAD_REQUEST,
+               "Request with incorrect parameters: " + illArg.getMessage());
+         return;
+      }
+      catch(Exception ex)
+      {
+         LOGGER.info("Exception: " + ex.getMessage());
+         ex.printStackTrace();
+      }
+
+      return;
+   }
+
+
+   private void legacyLogEntry(HttpServletRequest request) throws UnsupportedEncodingException
+   {
+      StringBuffer requestURL = request.getRequestURL();
+      if (request.getQueryString() != null)
+      {
+         requestURL.append("?").append(request.getQueryString());
+         String completeURL = requestURL.toString();
+         String className = this.getClass().getSimpleName();
+         LOGGER.info(className + " vlkb req from: "
+               + request.getRemoteAddr()
+               + " doGet: " + URLDecoder.decode(completeURL, "UTF-8"));
+      }
+   }
+
+
+}
+
diff --git a/data-discovery/src/main/java/vlkb/webapi/SearchSettings.java b/data-discovery/src/main/java/vlkb/webapi/SearchSettings.java
new file mode 100644
index 0000000000000000000000000000000000000000..5d9db4379a8a971433f6b2bfde1966f6bfec874f
--- /dev/null
+++ b/data-discovery/src/main/java/vlkb/webapi/SearchSettings.java
@@ -0,0 +1,122 @@
+
+import java.util.logging.Logger;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+import java.io.PrintWriter;
+
+
+class SearchSettings
+{
+   private static final Logger LOGGER = Logger.getLogger("SearchSettings");
+
+
+   public static class DBConn
+   {
+      private String uri;
+      private String schema;
+      private String user_name;
+      private String password;
+
+      public String uri() {return uri;}
+      public String schema() {return schema;}
+      public String userName() {return user_name;}
+      public String password() {return password;}
+
+      public String toString()
+      {
+         return uri + "  schema[" + schema +  "] " + user_name + " / " + password;
+      }
+   }
+
+
+   public static class ServiceUrls
+   {
+      private String cutoutUrl;
+      private String mergeUrl;
+      private String surveysAbsPathname;
+
+      public boolean cutoutUrlIsSet() { return (cutoutUrl != null) && cutoutUrl.trim().isEmpty(); }
+      public boolean mergeUrlIsSet()  { return (mergeUrl != null) && mergeUrl.trim().isEmpty(); }
+      public boolean surveysAbsPathnameIsSet()
+       { return (surveysAbsPathname != null) && surveysAbsPathname.trim().isEmpty(); }
+
+      public String cutoutUrl() {return cutoutUrl;}
+      public String mergeUrl()  {return mergeUrl;}
+      public String surveysAbsPathname()  {return surveysAbsPathname;}
+
+      public String toString()
+      {
+         return cutoutUrl + "   "  + mergeUrl + "   " + surveysAbsPathname;
+      }
+   }
+
+
+   public DBConn     dbConn;
+   public ServiceUrls serviceUrls;
+
+
+   // will not start without config-file; no reasonable code-defaults can be invented
+   public static SearchSettings getInstance(String settingsFileName)
+   {
+      try
+      {
+         InputStream ins = SearchSettings.class.getClassLoader().getResourceAsStream(settingsFileName);
+
+         if (ins != null)
+         {
+            Properties properties = new Properties();
+            properties.load(ins);
+
+            DBConn      dbConn      = loadDBConn(properties);
+            ServiceUrls serviceUrls = loadServiceUrls(properties);
+
+            return new SearchSettings(dbConn, serviceUrls);
+         }
+         else
+         {
+            throw new IllegalStateException(settingsFileName + " not found in classpath");
+         }
+
+      }
+      catch(IOException ex)
+      {
+         throw new IllegalStateException("Error while loading " + settingsFileName + " file", ex);
+      }
+   }
+
+
+
+
+   private SearchSettings(DBConn dbConn, ServiceUrls serviceUrls)
+   {
+      this.dbConn      = dbConn;
+      this.serviceUrls = serviceUrls;
+   }
+
+
+   private static DBConn loadDBConn(Properties properties)
+   {
+      DBConn dbConn = new SearchSettings.DBConn();
+      dbConn.uri       = properties.getProperty("db_uri","jdbc:postgresql://localhost:5432/vialactea").strip();
+      dbConn.schema    = properties.getProperty("db_schema","datasets").strip();
+      dbConn.user_name = properties.getProperty("db_user_name","").strip();
+      dbConn.password  = properties.getProperty("db_password","").strip();
+
+      return dbConn;
+   }
+
+
+   private static ServiceUrls loadServiceUrls(Properties properties)
+   {
+      ServiceUrls serviceUrls = new ServiceUrls();
+      serviceUrls.cutoutUrl = properties.getProperty("cutout_url","").strip();
+      serviceUrls.mergeUrl  = properties.getProperty("merge_url","").strip();
+      serviceUrls.surveysAbsPathname = properties.getProperty("surveys_metadata_abs_pathname","/srv/surveys/surveys_metadata.csv").strip();
+      return serviceUrls;
+   }
+
+
+}
+
diff --git a/data-discovery/src/main/resources/authpolicy.properties b/data-discovery/src/main/resources/authpolicy.properties
new file mode 100644
index 0000000000000000000000000000000000000000..d1d5756218a28b49df6e1f92a8828c9f62c24cac
--- /dev/null
+++ b/data-discovery/src/main/resources/authpolicy.properties
@@ -0,0 +1,7 @@
+# database for table with permissions
+db_uri=
+db_schema=
+db_user_name=
+db_password=
+
+
diff --git a/data-discovery/src/main/resources/discovery.properties b/data-discovery/src/main/resources/discovery.properties
new file mode 100644
index 0000000000000000000000000000000000000000..0f01bd5616e5651e8e9810e6ea99ed22a93e0418
--- /dev/null
+++ b/data-discovery/src/main/resources/discovery.properties
@@ -0,0 +1,7 @@
+
+# database with 'obscore' table
+db_uri=jdbc:postgresql://localhost:5432/vialactea
+db_schema=datasets
+db_user_name=
+db_password=
+
diff --git a/data-discovery/src/main/resources/formatresponsefilter.properties b/data-discovery/src/main/resources/formatresponsefilter.properties
new file mode 100644
index 0000000000000000000000000000000000000000..b5d903d4bd7b2d58da2643baa33db2c73ebc0471
--- /dev/null
+++ b/data-discovery/src/main/resources/formatresponsefilter.properties
@@ -0,0 +1,13 @@
+
+# database with 'surveys' table of metadata used to write VLKB-legacy response.xml
+#db_uri=jdbc:postgresql://localhost:5432/vialactea
+#db_schema=datasets
+#db_user_name=
+#db_password=
+# FIXME above disabled; now read metadata from a file
+surveys_abs_pathname=
+
+# these URL's are used in response.xml so client can access those services
+cutout_url=
+merge_url=
+
diff --git a/data-discovery/src/main/webapp/WEB-INF/web-search-garrtoken.xml b/data-discovery/src/main/webapp/WEB-INF/web-search-garrtoken.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b814b2a15fea714d2b1f96a45af394904ddc9258
--- /dev/null
+++ b/data-discovery/src/main/webapp/WEB-INF/web-search-garrtoken.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Copyright 2004-2005 Sun Microsystems, Inc.  All rights reserved.
+ Use is subject to license terms.
+-->
+
+<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
+        <display-name>Via Lactea. Query FITS datacubes.</display-name>
+        <distributable/>
+
+
+        <filter>
+                <filter-name>FormatResponseFilter</filter-name>
+                <filter-class>FormatResponseFilter</filter-class>
+        </filter>
+        <filter-mapping>
+                <filter-name>FormatResponseFilter</filter-name>
+                <url-pattern>/*</url-pattern>
+        </filter-mapping>
+
+
+
+        <filter>
+                <filter-name>TokenFilter</filter-name>
+                <filter-class>NeaTokenFilter</filter-class>
+        </filter>
+        <filter-mapping>
+                <filter-name>TokenFilter</filter-name>
+                <url-pattern>/*</url-pattern>
+        </filter-mapping>
+
+        <filter>
+                <filter-name>AuthorizationResponseFilter</filter-name>
+                <filter-class>AuthorizationResponseFilter</filter-class>
+        </filter>
+        <filter-mapping>
+                <filter-name>AuthorizationResponseFilter</filter-name>
+                <url-pattern>/*</url-pattern>
+        </filter-mapping>
+
+
+
+<servlet>
+   <servlet-name>vlkb_search</servlet-name>
+   <servlet-class>SearchServlet</servlet-class>
+</servlet>
+<servlet-mapping>
+   <servlet-name>vlkb_search</servlet-name>
+   <url-pattern></url-pattern>
+</servlet-mapping>
+
+
+<servlet>
+   <servlet-name>vlkb_vosi_availability</servlet-name>
+   <servlet-class>VlkbServletFile</servlet-class>
+</servlet>
+<servlet-mapping>
+   <servlet-name>vlkb_vosi_availability</servlet-name>
+   <url-pattern>/availability</url-pattern>
+</servlet-mapping>
+
+
+<servlet>
+   <servlet-name>vlkb_vosi_capabilities</servlet-name>
+   <servlet-class>VlkbServletFile</servlet-class>
+</servlet>
+<servlet-mapping>
+   <servlet-name>vlkb_vosi_capabilities</servlet-name>
+   <url-pattern>/capabilities</url-pattern>
+</servlet-mapping>
+
+
+</web-app>
+
+
diff --git a/data-discovery/src/main/webapp/WEB-INF/web-search-ia2token.xml b/data-discovery/src/main/webapp/WEB-INF/web-search-ia2token.xml
new file mode 100644
index 0000000000000000000000000000000000000000..54dd67150f867fae0c6a069aa495c96c84f72dd8
--- /dev/null
+++ b/data-discovery/src/main/webapp/WEB-INF/web-search-ia2token.xml
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Copyright 2004-2005 Sun Microsystems, Inc.  All rights reserved.
+ Use is subject to license terms.
+-->
+
+<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
+        <display-name>Via Lactea. Query FITS datacubes.</display-name>
+        <distributable/>
+
+
+        <filter>
+                <filter-name>FormatResponseFilter</filter-name>
+                <filter-class>FormatResponseFilter</filter-class>
+        </filter>
+        <filter-mapping>
+                <filter-name>FormatResponseFilter</filter-name>
+                <url-pattern>/*</url-pattern>
+        </filter-mapping>
+
+
+
+       <filter>
+               <filter-name>TokenFilter</filter-name>
+               <filter-class>it.inaf.ia2.aa.TokenFilter</filter-class>
+       </filter>
+
+       <filter-mapping>
+               <filter-name>TokenFilter</filter-name>
+               <url-pattern>/*</url-pattern>
+       </filter-mapping>
+
+       <filter>
+               <filter-name>UserTypeConverter</filter-name>
+               <filter-class>IA2TokenConvFilter</filter-class>
+       </filter>
+
+       <filter-mapping>
+               <filter-name>UserTypeConverter</filter-name>
+               <url-pattern>/*</url-pattern>
+       </filter-mapping>
+
+        <filter>
+                <filter-name>AuthorizationResponseFilter</filter-name>
+                <filter-class>AuthorizationResponseFilter</filter-class>
+        </filter>
+        <filter-mapping>
+                <filter-name>AuthorizationResponseFilter</filter-name>
+                <url-pattern>/*</url-pattern>
+        </filter-mapping>
+
+
+
+<servlet>
+   <servlet-name>vlkb_search</servlet-name>
+   <servlet-class>SearchServlet</servlet-class>
+</servlet>
+<servlet-mapping>
+   <servlet-name>vlkb_search</servlet-name>
+   <url-pattern></url-pattern>
+</servlet-mapping>
+
+
+<servlet>
+   <servlet-name>vlkb_vosi_availability</servlet-name>
+   <servlet-class>VlkbServletFile</servlet-class>
+</servlet>
+<servlet-mapping>
+   <servlet-name>vlkb_vosi_availability</servlet-name>
+   <url-pattern>/availability</url-pattern>
+</servlet-mapping>
+
+
+<servlet>
+   <servlet-name>vlkb_vosi_capabilities</servlet-name>
+   <servlet-class>VlkbServletFile</servlet-class>
+</servlet>
+<servlet-mapping>
+   <servlet-name>vlkb_vosi_capabilities</servlet-name>
+   <url-pattern>/capabilities</url-pattern>
+</servlet-mapping>
+
+
+</web-app>
+
+
diff --git a/data-discovery/src/main/webapp/WEB-INF/web-search-iamtoken.xml b/data-discovery/src/main/webapp/WEB-INF/web-search-iamtoken.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8cde90b5b438e1dfe1d7884f32b5814be86d7645
--- /dev/null
+++ b/data-discovery/src/main/webapp/WEB-INF/web-search-iamtoken.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Copyright 2004-2005 Sun Microsystems, Inc.  All rights reserved.
+ Use is subject to license terms.
+-->
+
+<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
+        <display-name>Via Lactea. Query FITS datacubes.</display-name>
+        <distributable/>
+
+
+        <filter>
+                <filter-name>FormatResponseFilter</filter-name>
+                <filter-class>FormatResponseFilter</filter-class>
+        </filter>
+        <filter-mapping>
+                <filter-name>FormatResponseFilter</filter-name>
+                <url-pattern>/*</url-pattern>
+        </filter-mapping>
+
+
+
+        <filter>
+                <filter-name>TokenFilter</filter-name>
+                <filter-class>IamTokenFilter</filter-class>
+        </filter>
+        <filter-mapping>
+                <filter-name>TokenFilter</filter-name>
+                <url-pattern>/*</url-pattern>
+        </filter-mapping>
+
+        <filter>
+                <filter-name>AuthorizationResponseFilter</filter-name>
+                <filter-class>AuthorizationResponseFilter</filter-class>
+        </filter>
+        <filter-mapping>
+                <filter-name>AuthorizationResponseFilter</filter-name>
+                <url-pattern>/*</url-pattern>
+        </filter-mapping>
+
+
+
+<servlet>
+   <servlet-name>vlkb_search</servlet-name>
+   <servlet-class>SearchServlet</servlet-class>
+</servlet>
+<servlet-mapping>
+   <servlet-name>vlkb_search</servlet-name>
+   <url-pattern></url-pattern>
+</servlet-mapping>
+
+
+<servlet>
+   <servlet-name>vlkb_vosi_availability</servlet-name>
+   <servlet-class>VlkbServletFile</servlet-class>
+</servlet>
+<servlet-mapping>
+   <servlet-name>vlkb_vosi_availability</servlet-name>
+   <url-pattern>/availability</url-pattern>
+</servlet-mapping>
+
+
+<servlet>
+   <servlet-name>vlkb_vosi_capabilities</servlet-name>
+   <servlet-class>VlkbServletFile</servlet-class>
+</servlet>
+<servlet-mapping>
+   <servlet-name>vlkb_vosi_capabilities</servlet-name>
+   <url-pattern>/capabilities</url-pattern>
+</servlet-mapping>
+
+
+</web-app>
+
+
diff --git a/data-discovery/src/main/webapp/WEB-INF/web.xml b/data-discovery/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000000000000000000000000000000000000..bdd5291f861b2559bc588a23b7250aae7a3ce2ad
--- /dev/null
+++ b/data-discovery/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Copyright 2004-2005 Sun Microsystems, Inc.  All rights reserved.
+ Use is subject to license terms.
+-->
+
+<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
+        <display-name>Via Lactea. Query FITS datacubes.</display-name>
+        <distributable/>
+
+
+        <filter>
+                <filter-name>FormatResponseFilter</filter-name>
+                <filter-class>FormatResponseFilter</filter-class>
+        </filter>
+        <filter-mapping>
+                <filter-name>FormatResponseFilter</filter-name>
+                <url-pattern>/*</url-pattern>
+        </filter-mapping>
+
+
+<!-- uncomment IA2 or GARR authorization filter to enable security
+
+       <filter>
+               <filter-name>TokenFilter</filter-name>
+               <filter-class>it.inaf.ia2.aa.TokenFilter</filter-class>
+       </filter>
+
+       <filter-mapping>
+               <filter-name>TokenFilter</filter-name>
+               <url-pattern>/*</url-pattern>
+       </filter-mapping>
+
+       <filter>
+               <filter-name>UserTypeConverter</filter-name>
+               <filter-class>IA2TokenConvFilter</filter-class>
+       </filter>
+
+       <filter-mapping>
+               <filter-name>UserTypeConverter</filter-name>
+               <url-pattern>/*</url-pattern>
+       </filter-mapping>
+
+
+
+        <filter>
+                <filter-name>TokenFilter</filter-name>
+                <filter-class>NeaAuthFilter</filter-class>
+        </filter>
+        <filter-mapping>
+                <filter-name>TokenFilter</filter-name>
+                <url-pattern>/*</url-pattern>
+        </filter-mapping>
+-->
+
+<!-- additionally uncomment this to enable groups-based authorization check
+        <filter>
+                <filter-name>AuthorizationResponseFilter</filter-name>
+                <filter-class>AuthorizationResponseFilter</filter-class>
+        </filter>
+        <filter-mapping>
+                <filter-name>AuthorizationResponseFilter</filter-name>
+                <url-pattern>/*</url-pattern>
+        </filter-mapping>
+-->
+
+
+
+<servlet>
+   <servlet-name>vlkb_search</servlet-name>
+   <servlet-class>SearchServlet</servlet-class>
+</servlet>
+<servlet-mapping>
+   <servlet-name>vlkb_search</servlet-name>
+   <url-pattern></url-pattern>
+</servlet-mapping>
+
+
+<servlet>
+   <servlet-name>vlkb_vosi_availability</servlet-name>
+   <servlet-class>VlkbServletFile</servlet-class>
+</servlet>
+<servlet-mapping>
+   <servlet-name>vlkb_vosi_availability</servlet-name>
+   <url-pattern>/availability</url-pattern>
+</servlet-mapping>
+
+
+<servlet>
+   <servlet-name>vlkb_vosi_capabilities</servlet-name>
+   <servlet-class>VlkbServletFile</servlet-class>
+</servlet>
+<servlet-mapping>
+   <servlet-name>vlkb_vosi_capabilities</servlet-name>
+   <url-pattern>/capabilities</url-pattern>
+</servlet-mapping>
+
+
+</web-app>
+
+
diff --git a/data-discovery/src/main/webapp/index.html b/data-discovery/src/main/webapp/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..ca0faba0a17096767e670a008b2136879303e371
--- /dev/null
+++ b/data-discovery/src/main/webapp/index.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+ "http://www.w3c.org/TR/xhtml1/DTD/xhtml11.dtd">
+
+<html xmlns="http://www.w3.org/1999/XHTML"
+      xml:lang="it" lang="it">
+  <head>
+    <title>ViaLactea: query FITS files</title>
+    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
+    <script src="xml2html.js"></script>
+  </head>
+  <body>
+    <div>
+      Space axes, galactic coordinates [l,b] and radius r, defines circle:<br />
+      <br />
+      [l,b]: <input id="lon" type="text" name="l" value="-78.8" />
+             <input id="lat" type="text" name="b" value="-1.6" />[deg]<br />
+      r: <input id="radius"  type="text" name="r" value="0.1" />[deg]<br />
+      <br />
+      Spectral axis, velocity bounds:<br />
+      <br />
+      [vlow,vup]: <input id="vl" type="text" name="vl" value="-30.0" />
+      <input id="vu" type="text" name="vu" value="20.0" />[km/s]<br />
+      <p>
+        <!--<input type="submit" value="Invia &gt;&gt;" />-->
+        <button type="button" id="invButton">Search</button>
+      </p>
+    </div>
+    
+    <div id="gendHtml" > </div>
+  </body>
+</html>
diff --git a/data-discovery/src/main/webapp/index.jsp b/data-discovery/src/main/webapp/index.jsp
new file mode 100644
index 0000000000000000000000000000000000000000..c38169bb958579c635a5c09ee2f379cc5956c0c2
--- /dev/null
+++ b/data-discovery/src/main/webapp/index.jsp
@@ -0,0 +1,5 @@
+<html>
+<body>
+<h2>Hello World!</h2>
+</body>
+</html>
diff --git a/data-discovery/src/main/webapp/vlkb-search.xsl b/data-discovery/src/main/webapp/vlkb-search.xsl
new file mode 100644
index 0000000000000000000000000000000000000000..7f2290f4255d6c7b4d67874dc04907349b4cc926
--- /dev/null
+++ b/data-discovery/src/main/webapp/vlkb-search.xsl
@@ -0,0 +1,88 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ xmlns:fo="http://www.w3.org/1999/XSL/Format">
+
+<xsl:template match="/">
+<html>
+<body>
+<!--
+<table border="2">
+<Caption>Inputs: </Caption>
+<tr>
+<th>l [deg]</th>
+<th>b [deg]</th>
+<th>r [deg]</th>
+</tr>
+<xsl:for-each select="results/input">
+<tr>
+<td><xsl:value-of select="l"/></td>
+<td><xsl:value-of select="b"/></td>
+<td><xsl:value-of select="r"/></td>
+</tr>
+</xsl:for-each>
+</table>
+-->
+<table border="1">
+<Caption> Search Results </Caption>
+<tr>
+<!--
+<th>Survey</th>
+<th>Species</th>
+<th>Transition</th>
+-->
+<th>PublisherDID</th>
+<th>P1 lon</th>
+<th>P1 lat</th>
+<th>P2 lon</th>
+<th>P2 lat</th>
+<th>P3 lon</th>
+<th>P3 lat</th>
+<th>P4 lon</th>
+<th>P4 lat</th>
+<!-- <th>Link</th> -->
+</tr>
+
+<xsl:for-each select="results/survey/datacube">
+        <tr>
+                <!--
+<td><xsl:value-of select="Survey"/></td>
+<td><xsl:value-of select="Species"/></td>
+<td><xsl:value-of select="Transition"/></td>
+-->
+<td>
+    <a href="{Access/URL[@type='cutout']}">
+                    <xsl:value-of select="PublisherDID"/>
+    </a>
+</td>
+<td><xsl:value-of select="vertices/SkyCoordSystem/P1/longitude"/></td>
+<td><xsl:value-of select="vertices/SkyCoordSystem/P1/latitude"/></td>
+<td><xsl:value-of select="vertices/SkyCoordSystem/P2/longitude"/></td>
+<td><xsl:value-of select="vertices/SkyCoordSystem/P2/latitude"/></td>
+<td><xsl:value-of select="vertices/SkyCoordSystem/P3/longitude"/></td>
+<td><xsl:value-of select="vertices/SkyCoordSystem/P3/latitude"/></td>
+<td><xsl:value-of select="vertices/SkyCoordSystem/P4/longitude"/></td>
+<td><xsl:value-of select="vertices/SkyCoordSystem/P4/latitude"/></td>
+<!-- <td><xsl:value-of select="Access/URL[@type='cutout']"/></td> -->
+</tr>
+
+</xsl:for-each>
+</table>
+
+</body>
+</html>
+</xsl:template>
+
+
+
+
+
+
+
+
+
+
+
+
+
+</xsl:stylesheet>
diff --git a/data-discovery/src/main/webapp/xml2html.js b/data-discovery/src/main/webapp/xml2html.js
new file mode 100644
index 0000000000000000000000000000000000000000..69ab5268bd81a694153ff2fe11c4698619aab431
--- /dev/null
+++ b/data-discovery/src/main/webapp/xml2html.js
@@ -0,0 +1,132 @@
+
+$(document).ready(function() {
+
+    function doXSLT(xml, xslt) {
+     if(typeof XSLTProcessor !== "undefined") { // FF, Safari, Chrome etc
+         var xmlDoc = xml;//$.parseXML(xml);
+         var xsltDoc = xslt;//$.parseXML(xslt);
+         var xsltProcessor = new XSLTProcessor();
+         xsltProcessor.importStylesheet(xsltDoc);
+         var xmlFragment = xsltProcessor.transformToFragment(xmlDoc, document);
+         return XMLToString(xmlFragment);
+     }
+    
+     var xmlDoc = StringToXML(xml);
+     var xsltDoc = StringToXML(xslt);
+     //console.log("xmlDoc=",xmlDoc.xml);
+
+     if (typeof (xmlDoc.transformNode) != "undefined") { // IE6, IE7, IE8
+         return xmlDoc.transformNode(xsltDoc);
+     } else {
+         try { // IE9 and grater
+             if (window.ActiveXObject != "undefined") {
+                 var xslt_w = new ActiveXObject("Msxml2.XSLTemplate");
+                 var xslDoc = new ActiveXObject("Msxml2.FreeThreadedDOMDocument");
+                 //console.log("xslt="+xsltDoc.xml);
+                 xslDoc.loadXML(xslt);
+                 xslt_w.stylesheet = xslDoc;
+                 var xslProc = xslt_w.createProcessor();
+                 xslProc.input = xmlDoc;
+                 xslProc.transform();
+                 var x = xslProc.output;
+                 return xslProc.output;
+             }
+         }
+         catch (e) {
+             alert(e);
+             return null;
+         }
+     }
+  }
+
+
+function XMLToString(oXML) {
+   if ("ActiveXObject" in window) { //code for IE
+     var oString = oXML.xml; return oString;
+   } else {
+     return (new XMLSerializer()).serializeToString(oXML);
+   }
+}
+/*
+function StringToXML(oString) {
+  if ("ActiveXObject" in window) { //code for IE
+   var oXML = new ActiveXObject("Microsoft.XMLDOM");
+   oXML.loadXML(oString);
+   if (oXML.parseError.errorCode != 0)
+     {
+     alert("Error in line " + oXML.parseError.line +
+     " position " + oXML.parseError.linePos +
+     "\nError Code: " + oXML.parseError.errorCode +
+     "\nError Reason: " + oXML.parseError.reason +
+     "Error Line: " + oXML.parseError.srcText);
+     //return(null);
+     }
+   return oXML;
+  } else { // code for Chrome, Safari, Firefox, Opera, etc.
+   return (new DOMParser()).parseFromString(oString, "text/xml");
+  }
+}
+*/
+
+
+
+  $('#invButton').click(function(){
+    console.log('pressed');
+    
+    var path = location.origin + location.pathname.replace("galactic.html", "");
+    
+    $.get(path + 'vlkb-search.xsl?v=' + (new Date()).getTime(), function(result) {
+//    $.get('http://localhost:8080/libjnifitsdb-0.22.0/vlkb-search.xsl', function(result) {
+      var xslt = result;
+      
+      var lon = $('#lon').val();
+      var lat = $('#lat').val();
+      var radius = $('#radius').val();
+      var vl = $('#vl').val();
+      var vu = $('#vu').val();
+      
+      var jsonpar = {
+       l: lon,
+       b: lat,
+       r: radius
+      };
+      if(vl !== '') {
+         jsonpar['vl'] = vl;
+      }
+      if(vu !== '') {
+         jsonpar['vu'] = vu;
+      }
+      
+      console.log(jsonpar);
+      
+//      $.get(path + 'vlkb_search?l=' + lon + '&b=' + lat + '&r=' + radius + '&vl=' + vl  + '&vu=' + vu,
+      
+      $.get(path + 'vlkb_search', jsonpar, function(result) {
+         var xml = result;
+         //console.log(xml);
+         var htmlRes = $('<div>' + (new Date()).toLocaleString() + doXSLT(xml, xslt) + '</div>');
+         $('#gendHtml').html(htmlRes);
+
+/* format 2 decimals         
+         var ths = $('table:nth-child(2) th');
+         
+         for(var i=0; i<ths.length; i++) {
+         
+            var th = ths[i];
+            if($(th).text() === 'vel-from')  {
+              var tds = $('table:nth-child(2) tr td:nth-child(' + (i+1) + ')');
+              for(var j=0; j< tds.length; j++) {
+                 var td = tds[j];                                
+                 $(td).text(parseFloat($(td).text()).toFixed(2));
+
+              }
+              break;
+            }
+         }
+*/
+      });
+    });
+  });
+
+});
+
diff --git a/java-libs/jjwt-api-0.11.2.jar b/java-libs/jjwt-api-0.11.2.jar
new file mode 100644
index 0000000000000000000000000000000000000000..15f236c815d33220a193cc5e3c26bab5e448683b
Binary files /dev/null and b/java-libs/jjwt-api-0.11.2.jar differ
diff --git a/java-libs/jjwt-impl-0.11.2.jar b/java-libs/jjwt-impl-0.11.2.jar
new file mode 100644
index 0000000000000000000000000000000000000000..3594cb18f648035464f9ef3866c68de5a4f01e42
Binary files /dev/null and b/java-libs/jjwt-impl-0.11.2.jar differ
diff --git a/java-libs/jjwt-jackson-0.11.2.jar b/java-libs/jjwt-jackson-0.11.2.jar
new file mode 100644
index 0000000000000000000000000000000000000000..f081d3f964a9700849c56380951d679e8d5bd24b
Binary files /dev/null and b/java-libs/jjwt-jackson-0.11.2.jar differ
diff --git a/java-libs/lib/FastInfoset-1.2.16.jar b/java-libs/lib/FastInfoset-1.2.16.jar
new file mode 100644
index 0000000000000000000000000000000000000000..6e91f1cec27721fd3d6b35522099cca9b686211d
Binary files /dev/null and b/java-libs/lib/FastInfoset-1.2.16.jar differ
diff --git a/java-libs/lib/auth-lib-2.0.0-SNAPSHOT.jar b/java-libs/lib/auth-lib-2.0.0-SNAPSHOT.jar
new file mode 100644
index 0000000000000000000000000000000000000000..b25b368692395a76cae52fb5a72998728d198e38
Binary files /dev/null and b/java-libs/lib/auth-lib-2.0.0-SNAPSHOT.jar differ
diff --git a/java-libs/lib/cache2k-api-1.2.4.Final.jar b/java-libs/lib/cache2k-api-1.2.4.Final.jar
new file mode 100644
index 0000000000000000000000000000000000000000..8075b2757c7843e83ba5d19f08b166a3b612f19e
Binary files /dev/null and b/java-libs/lib/cache2k-api-1.2.4.Final.jar differ
diff --git a/java-libs/lib/cache2k-core-1.2.4.Final.jar b/java-libs/lib/cache2k-core-1.2.4.Final.jar
new file mode 100644
index 0000000000000000000000000000000000000000..c9133750cd6b43168402a716bf7e378ccc2cad74
Binary files /dev/null and b/java-libs/lib/cache2k-core-1.2.4.Final.jar differ
diff --git a/java-libs/lib/commons-beanutils-1.8.3.jar b/java-libs/lib/commons-beanutils-1.8.3.jar
new file mode 100644
index 0000000000000000000000000000000000000000..218510bc5d65ac161aca7e4b2cb575fbbee5d313
Binary files /dev/null and b/java-libs/lib/commons-beanutils-1.8.3.jar differ
diff --git a/java-libs/lib/commons-lang3-3.12.0.jar b/java-libs/lib/commons-lang3-3.12.0.jar
new file mode 100644
index 0000000000000000000000000000000000000000..4d434a2a4554815584365348ea2cf00cdfe3d5f9
Binary files /dev/null and b/java-libs/lib/commons-lang3-3.12.0.jar differ
diff --git a/java-libs/lib/fits.jar b/java-libs/lib/fits.jar
new file mode 100644
index 0000000000000000000000000000000000000000..bcd410a9b339c07ad29e3ea2a5dc0d16783ac7de
Binary files /dev/null and b/java-libs/lib/fits.jar differ
diff --git a/java-libs/lib/istack-commons-runtime-3.0.8.jar b/java-libs/lib/istack-commons-runtime-3.0.8.jar
new file mode 100644
index 0000000000000000000000000000000000000000..8f37e950bec36e0d8b34697ce2e9e57bb2d325cb
Binary files /dev/null and b/java-libs/lib/istack-commons-runtime-3.0.8.jar differ
diff --git a/java-libs/lib/jackson-annotations-2.9.10.jar b/java-libs/lib/jackson-annotations-2.9.10.jar
new file mode 100644
index 0000000000000000000000000000000000000000..de054c66b25e559d868336a9ca85c1ce663673b1
Binary files /dev/null and b/java-libs/lib/jackson-annotations-2.9.10.jar differ
diff --git a/java-libs/lib/jackson-core-2.9.10.jar b/java-libs/lib/jackson-core-2.9.10.jar
new file mode 100644
index 0000000000000000000000000000000000000000..1b5e87ccc0adde69fc1499539a29e87a0f416832
Binary files /dev/null and b/java-libs/lib/jackson-core-2.9.10.jar differ
diff --git a/java-libs/lib/jackson-databind-2.9.10.4.jar b/java-libs/lib/jackson-databind-2.9.10.4.jar
new file mode 100644
index 0000000000000000000000000000000000000000..9045f2f40240a4b62ec959b8440d10165f0581ea
Binary files /dev/null and b/java-libs/lib/jackson-databind-2.9.10.4.jar differ
diff --git a/java-libs/lib/jakarta.activation-api-1.2.1.jar b/java-libs/lib/jakarta.activation-api-1.2.1.jar
new file mode 100644
index 0000000000000000000000000000000000000000..bbfb52ff01e082afd65ee6444f2645e999f98ee0
Binary files /dev/null and b/java-libs/lib/jakarta.activation-api-1.2.1.jar differ
diff --git a/java-libs/lib/jakarta.json-1.1.5.jar b/java-libs/lib/jakarta.json-1.1.5.jar
new file mode 100644
index 0000000000000000000000000000000000000000..f96ee9b05650b1bf38f31e554191d8c312bd8213
Binary files /dev/null and b/java-libs/lib/jakarta.json-1.1.5.jar differ
diff --git a/java-libs/lib/jakarta.json-api-1.1.5.jar b/java-libs/lib/jakarta.json-api-1.1.5.jar
new file mode 100644
index 0000000000000000000000000000000000000000..50995a4584e03fcf3a8726fd3f19d80386dee961
Binary files /dev/null and b/java-libs/lib/jakarta.json-api-1.1.5.jar differ
diff --git a/java-libs/lib/jakarta.json.bind-api-1.0.1.jar b/java-libs/lib/jakarta.json.bind-api-1.0.1.jar
new file mode 100644
index 0000000000000000000000000000000000000000..1dbfe8c848b531165b322438af2f206209e93e06
Binary files /dev/null and b/java-libs/lib/jakarta.json.bind-api-1.0.1.jar differ
diff --git a/java-libs/lib/jakarta.xml.bind-api-2.3.2.jar b/java-libs/lib/jakarta.xml.bind-api-2.3.2.jar
new file mode 100644
index 0000000000000000000000000000000000000000..b16236d561c08c177f79a99e2c7a47be1f81616d
Binary files /dev/null and b/java-libs/lib/jakarta.xml.bind-api-2.3.2.jar differ
diff --git a/java-libs/lib/javax.servlet-api-3.1.0.jar b/java-libs/lib/javax.servlet-api-3.1.0.jar
new file mode 100644
index 0000000000000000000000000000000000000000..6b14c3d267867e76c04948bb31b3de18e01412ee
Binary files /dev/null and b/java-libs/lib/javax.servlet-api-3.1.0.jar differ
diff --git a/java-libs/lib/jaxb-runtime-2.3.2.jar b/java-libs/lib/jaxb-runtime-2.3.2.jar
new file mode 100644
index 0000000000000000000000000000000000000000..62f8719664cb8db795d47b0ab5f323455bb7461a
Binary files /dev/null and b/java-libs/lib/jaxb-runtime-2.3.2.jar differ
diff --git a/java-libs/lib/jcl-over-slf4j-1.7.5.jar b/java-libs/lib/jcl-over-slf4j-1.7.5.jar
new file mode 100644
index 0000000000000000000000000000000000000000..90153b069614af87e7bc9664c6d8e4d1414c2dff
Binary files /dev/null and b/java-libs/lib/jcl-over-slf4j-1.7.5.jar differ
diff --git a/java-libs/lib/jjwt-api-0.12.3.jar b/java-libs/lib/jjwt-api-0.12.3.jar
new file mode 100644
index 0000000000000000000000000000000000000000..28a50518532b198f97aeaecd2bc7b04783544fa7
Binary files /dev/null and b/java-libs/lib/jjwt-api-0.12.3.jar differ
diff --git a/java-libs/lib/jjwt-impl-0.12.3.jar b/java-libs/lib/jjwt-impl-0.12.3.jar
new file mode 100644
index 0000000000000000000000000000000000000000..458f09c2181a64c7f33bf025bd7c82004d6b57ef
Binary files /dev/null and b/java-libs/lib/jjwt-impl-0.12.3.jar differ
diff --git a/java-libs/lib/jjwt-jackson-0.12.3.jar b/java-libs/lib/jjwt-jackson-0.12.3.jar
new file mode 100644
index 0000000000000000000000000000000000000000..c7da9cddec6bd5fa0ef8ba4618a0837434862be0
Binary files /dev/null and b/java-libs/lib/jjwt-jackson-0.12.3.jar differ
diff --git a/java-libs/lib/json-simple-1.1.1.jar b/java-libs/lib/json-simple-1.1.1.jar
new file mode 100644
index 0000000000000000000000000000000000000000..dfd5856d0cad81dfe7845ea6ff4d170d8064d7b0
Binary files /dev/null and b/java-libs/lib/json-simple-1.1.1.jar differ
diff --git a/java-libs/lib/logback-classic-1.0.13.jar b/java-libs/lib/logback-classic-1.0.13.jar
new file mode 100644
index 0000000000000000000000000000000000000000..80bf5d15a20cb055ef1cad792663c2151c979fed
Binary files /dev/null and b/java-libs/lib/logback-classic-1.0.13.jar differ
diff --git a/java-libs/lib/logback-core-1.0.13.jar b/java-libs/lib/logback-core-1.0.13.jar
new file mode 100644
index 0000000000000000000000000000000000000000..568ccfaae59d083b2d3230e1b7aeeab012defa8d
Binary files /dev/null and b/java-libs/lib/logback-core-1.0.13.jar differ
diff --git a/java-libs/lib/mysql-connector-java-5.1.13-bin.jar b/java-libs/lib/mysql-connector-java-5.1.13-bin.jar
new file mode 100644
index 0000000000000000000000000000000000000000..6b5b2ba2694649cd8a79db3a34a5391338beb18f
Binary files /dev/null and b/java-libs/lib/mysql-connector-java-5.1.13-bin.jar differ
diff --git a/java-libs/lib/opencsv-5.7.1.jar b/java-libs/lib/opencsv-5.7.1.jar
new file mode 100644
index 0000000000000000000000000000000000000000..ea50aa6d7caae33fdd21bc42c95c851982f316a7
Binary files /dev/null and b/java-libs/lib/opencsv-5.7.1.jar differ
diff --git a/java-libs/lib/postgresql-42.2.5.jar b/java-libs/lib/postgresql-42.2.5.jar
new file mode 100644
index 0000000000000000000000000000000000000000..d89d4331a40f7a6c2d748774a8ad9a304a3d209d
Binary files /dev/null and b/java-libs/lib/postgresql-42.2.5.jar differ
diff --git a/java-libs/lib/rabbitmq-client.jar b/java-libs/lib/rabbitmq-client.jar
new file mode 100644
index 0000000000000000000000000000000000000000..e718167753cc7c337225e335c225af5e12583667
Binary files /dev/null and b/java-libs/lib/rabbitmq-client.jar differ
diff --git a/java-libs/lib/rap-client-1.0-SNAPSHOT.jar b/java-libs/lib/rap-client-1.0-SNAPSHOT.jar
new file mode 100644
index 0000000000000000000000000000000000000000..ea95f683e3e2f9a0feb2b28d843718966bdb3991
Binary files /dev/null and b/java-libs/lib/rap-client-1.0-SNAPSHOT.jar differ
diff --git a/java-libs/lib/shiro-core-1.2.2.jar b/java-libs/lib/shiro-core-1.2.2.jar
new file mode 100644
index 0000000000000000000000000000000000000000..35499f8da162a7022551a14a734dba7c17a7b939
Binary files /dev/null and b/java-libs/lib/shiro-core-1.2.2.jar differ
diff --git a/java-libs/lib/shiro-web-1.2.2.jar b/java-libs/lib/shiro-web-1.2.2.jar
new file mode 100644
index 0000000000000000000000000000000000000000..358c1102cef27dab2d88d15184a9cadcd549b49a
Binary files /dev/null and b/java-libs/lib/shiro-web-1.2.2.jar differ
diff --git a/java-libs/lib/slf4j-api-1.7.5.jar b/java-libs/lib/slf4j-api-1.7.5.jar
new file mode 100644
index 0000000000000000000000000000000000000000..8f004d3906fc9041ef1f6923b1bc9f7d522942b2
Binary files /dev/null and b/java-libs/lib/slf4j-api-1.7.5.jar differ
diff --git a/java-libs/lib/stax-ex-1.8.1.jar b/java-libs/lib/stax-ex-1.8.1.jar
new file mode 100644
index 0000000000000000000000000000000000000000..a200db53295bfa16e99e3063c98b6498f89f5939
Binary files /dev/null and b/java-libs/lib/stax-ex-1.8.1.jar differ
diff --git a/java-libs/lib/stil.jar b/java-libs/lib/stil.jar
new file mode 100644
index 0000000000000000000000000000000000000000..1c7d53a179b8dd7c4d50f91a028d533e4cf59aa6
Binary files /dev/null and b/java-libs/lib/stil.jar differ
diff --git a/java-libs/lib/txw2-2.3.2.jar b/java-libs/lib/txw2-2.3.2.jar
new file mode 100644
index 0000000000000000000000000000000000000000..0d5ac012dd14132a02347a8ad5fd8df363feca2a
Binary files /dev/null and b/java-libs/lib/txw2-2.3.2.jar differ
diff --git a/java-libs/lib/uws 4.4.jar b/java-libs/lib/uws 4.4.jar
new file mode 100644
index 0000000000000000000000000000000000000000..bcb92b4177679c1313b42f4f00f2fdc8bedd8239
Binary files /dev/null and b/java-libs/lib/uws 4.4.jar differ
diff --git a/java-libs/lib/yasson-1.0.3.jar b/java-libs/lib/yasson-1.0.3.jar
new file mode 100644
index 0000000000000000000000000000000000000000..dfa24dbe828a272788d93cf5801725d811252128
Binary files /dev/null and b/java-libs/lib/yasson-1.0.3.jar differ