userdata retrieval via json pointer

Change-Id: I9ce1cbd58bf93af226c25b9aa00c180033f5fb52
diff --git a/src/main/java/de/ids_mannheim/korap/config/JWTSigner.java b/src/main/java/de/ids_mannheim/korap/config/JWTSigner.java
index afca427..447ca17 100644
--- a/src/main/java/de/ids_mannheim/korap/config/JWTSigner.java
+++ b/src/main/java/de/ids_mannheim/korap/config/JWTSigner.java
@@ -7,8 +7,10 @@
 import com.nimbusds.jwt.SignedJWT;
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.user.GenericUserData;
 import de.ids_mannheim.korap.user.TokenContext;
 import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.user.Userdata;
 import de.ids_mannheim.korap.utils.TimeUtils;
 import org.joda.time.DateTime;
 
@@ -55,7 +57,9 @@
         cs.setIssuerClaim(this.issuer.toString());
 
         if ((scopes = (String) attr.get(Attributes.SCOPES)) != null) {
-            Scopes claims = Scopes.mapScopes(scopes, attr);
+            Userdata data = new GenericUserData();
+            data.readQuietly(attr, false);
+            Scopes claims = Scopes.mapScopes(scopes, data);
             cs.setCustomClaims(claims.toMap());
         }
 
diff --git a/src/main/java/de/ids_mannheim/korap/config/Scopes.java b/src/main/java/de/ids_mannheim/korap/config/Scopes.java
index aea1dbd..0262cc2 100644
--- a/src/main/java/de/ids_mannheim/korap/config/Scopes.java
+++ b/src/main/java/de/ids_mannheim/korap/config/Scopes.java
@@ -1,5 +1,6 @@
 package de.ids_mannheim.korap.config;
 
+import de.ids_mannheim.korap.user.Userdata;
 import de.ids_mannheim.korap.utils.JsonUtils;
 
 import java.util.ArrayList;
@@ -29,12 +30,12 @@
             Scope.preferences, Scope.search, Scope.queries };
 
 
-    public static Scopes getProfileScopes (Map<String, Object> values) {
+    public static Scopes getProfileScopes (Userdata values) {
         Scopes r = new Scopes();
         for (String key : profile) {
             Object v = values.get(key);
             if (v != null)
-                r._values.put(key, v);
+                r.values.put(key, v);
         }
         return r;
     }
@@ -55,39 +56,39 @@
     }
 
 
-    public static Scopes mapScopes (String scopes, Map<String, Object> details) {
+    public static Scopes mapScopes (String scopes, Userdata details) {
         Scopes m = new Scopes();
         if (scopes != null && !scopes.isEmpty()) {
             Scope[] scopearr = mapScopes(scopes);
             for (Scope s : scopearr) {
                 Object v = details.get(s.toString());
                 if (v != null)
-                    m._values.put(s.toString(), v);
+                    m.values.put(s.toString(), v);
             }
             if (scopes.contains(Scope.profile.toString()))
-                m._values.putAll(Scopes.getProfileScopes(details)._values);
-            m._values.put(Attributes.SCOPES, scopes);
+                m.values.putAll(Scopes.getProfileScopes(details).values);
+            m.values.put(Attributes.SCOPES, scopes);
         }
         return m;
     }
 
-    private Map<String, Object> _values;
+    private Map<String, Object> values;
 
 
     private Scopes () {
-        this._values = new HashMap<>();
+        this.values = new HashMap<>();
     }
 
 
     public String toEntity () {
-        if (this._values.isEmpty())
+        if (this.values.isEmpty())
             return "";
-        return JsonUtils.toJSON(this._values);
+        return JsonUtils.toJSON(this.values);
     }
 
 
     public Map<String, Object> toMap () {
-        return new HashMap<>(this._values);
+        return new HashMap<>(this.values);
     }
 
 }
diff --git a/src/main/java/de/ids_mannheim/korap/handlers/UserDetailsDao.java b/src/main/java/de/ids_mannheim/korap/handlers/UserDetailsDao.java
index 74a6f4a..a85383e 100644
--- a/src/main/java/de/ids_mannheim/korap/handlers/UserDetailsDao.java
+++ b/src/main/java/de/ids_mannheim/korap/handlers/UserDetailsDao.java
@@ -1,5 +1,6 @@
 package de.ids_mannheim.korap.handlers;
 
+import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.exceptions.dbException;
 import de.ids_mannheim.korap.interfaces.db.PersistenceClient;
@@ -32,11 +33,11 @@
 
 
     @Override
-    public int store (UserDetails data) {
+    public int store (UserDetails data) throws KustvaktException {
         String sql = "INSERT INTO user_details (user_id, data) VALUES (:userid, :data);";
         MapSqlParameterSource source = new MapSqlParameterSource();
         source.addValue("userid", data.getUserID());
-        source.addValue("data", data.data());
+        source.addValue("data", data.serialize());
 
         GeneratedKeyHolder gen = new GeneratedKeyHolder();
         try {
@@ -46,18 +47,17 @@
             return id;
         }
         catch (DataAccessException e) {
-            e.printStackTrace();
             return -1;
         }
     }
 
 
     @Override
-    public int update (UserDetails data) {
+    public int update (UserDetails data) throws KustvaktException {
         String sql = "UPDATE user_details SET data = :data WHERE user_id=:userid;";
         MapSqlParameterSource source = new MapSqlParameterSource();
         source.addValue("userid", data.getUserID());
-        source.addValue("data", data.data());
+        source.addValue("data", data.serialize());
 
         try {
             return this.jdbcTemplate.update(sql, source);
diff --git a/src/main/java/de/ids_mannheim/korap/handlers/UserSettingsDao.java b/src/main/java/de/ids_mannheim/korap/handlers/UserSettingsDao.java
index 7d02a60..266d39f 100644
--- a/src/main/java/de/ids_mannheim/korap/handlers/UserSettingsDao.java
+++ b/src/main/java/de/ids_mannheim/korap/handlers/UserSettingsDao.java
@@ -1,5 +1,6 @@
 package de.ids_mannheim.korap.handlers;
 
+import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.exceptions.dbException;
 import de.ids_mannheim.korap.interfaces.db.PersistenceClient;
@@ -37,11 +38,11 @@
 
 
     @Override
-    public int store (UserSettings data) {
+    public int store (UserSettings data) throws KustvaktException {
         String sql = "INSERT INTO user_settings (user_id, data) VALUES (:userid, :data);";
         MapSqlParameterSource source = new MapSqlParameterSource();
         source.addValue("userid", data.getUserID());
-        source.addValue("data", data.data());
+        source.addValue("data", data.serialize());
 
         GeneratedKeyHolder gen = new GeneratedKeyHolder();
         try {
@@ -51,7 +52,6 @@
             return id;
         }
         catch (DataAccessException e) {
-            e.printStackTrace();
             jlog.error("couldn't store data in db for user with id '{}'",
                     data.getUserID());
             return -1;
@@ -60,11 +60,11 @@
 
 
     @Override
-    public int update (UserSettings data) {
+    public int update (UserSettings data) throws KustvaktException {
         String sql = "UPDATE user_settings SET data = :data WHERE user_id=:userid;";
         MapSqlParameterSource source = new MapSqlParameterSource();
         source.addValue("userid", data.getUserID());
-        source.addValue("data", data.data());
+        source.addValue("data", data.serialize());
 
         try {
             return this.jdbcTemplate.update(sql, source);
diff --git a/src/main/java/de/ids_mannheim/korap/security/auth/KustvaktAuthenticationManager.java b/src/main/java/de/ids_mannheim/korap/security/auth/KustvaktAuthenticationManager.java
index 8decf1d..ccb8092 100644
--- a/src/main/java/de/ids_mannheim/korap/security/auth/KustvaktAuthenticationManager.java
+++ b/src/main/java/de/ids_mannheim/korap/security/auth/KustvaktAuthenticationManager.java
@@ -538,11 +538,11 @@
                     user.getUsername());
             entHandler.createAccount(user);
             UserDetails details = new UserDetails(user.getId());
-            details.readDefaults(safeMap);
+            details.read(safeMap, true);
             details.checkRequired();
 
             UserSettings settings = new UserSettings(user.getId());
-            settings.readDefaults(safeMap);
+            settings.read(safeMap, true);
             settings.checkRequired();
 
             UserDataDbIface dao = BeansFactory.getTypeFactory()
@@ -582,7 +582,7 @@
         entHandler.createAccount(user);
 
         UserDetails d = new UserDetails(user.getId());
-        d.readDefaults(attributes);
+        d.read(attributes, true);
         d.checkRequired();
 
         UserDataDbIface dao = BeansFactory.getTypeFactory()
@@ -591,7 +591,7 @@
         dao.store(d);
 
         UserSettings s = new UserSettings(user.getId());
-        s.readDefaults(attributes);
+        s.read(attributes, true);
         s.checkRequired();
 
         dao = BeansFactory.getTypeFactory().getTypeInterfaceBean(userdatadaos,
diff --git a/src/main/java/de/ids_mannheim/korap/user/UserSettings.java b/src/main/java/de/ids_mannheim/korap/user/UserSettings.java
index 763b180..7149b4b 100644
--- a/src/main/java/de/ids_mannheim/korap/user/UserSettings.java
+++ b/src/main/java/de/ids_mannheim/korap/user/UserSettings.java
@@ -19,7 +19,6 @@
     }
 
 
-    //todo: define default fields and values --> so they can never be null!
     @Override
     public String[] defaultFields () {
         return new String[] { Attributes.DEFAULT_REL_FOUNDRY,
diff --git a/src/main/java/de/ids_mannheim/korap/user/Userdata.java b/src/main/java/de/ids_mannheim/korap/user/Userdata.java
index bb3250d..6a26f92 100644
--- a/src/main/java/de/ids_mannheim/korap/user/Userdata.java
+++ b/src/main/java/de/ids_mannheim/korap/user/Userdata.java
@@ -3,9 +3,7 @@
 import de.ids_mannheim.korap.exceptions.KustvaktException;
 import de.ids_mannheim.korap.exceptions.StatusCodes;
 import de.ids_mannheim.korap.interfaces.EncryptionIface;
-import de.ids_mannheim.korap.utils.JsonUtils;
 import lombok.AccessLevel;
-import lombok.Data;
 import lombok.Getter;
 import lombok.Setter;
 
@@ -15,51 +13,34 @@
  * @author hanl
  * @date 22/01/2016
  */
-@Data
 public abstract class Userdata {
 
+    @Getter
+    @Setter
     private Integer id;
     @Getter(AccessLevel.PRIVATE)
     @Setter(AccessLevel.PRIVATE)
-    private Map<String, Object> fields;
+    private Object data;
+    @Getter
     @Setter(AccessLevel.PRIVATE)
     private Integer userID;
 
 
     public Userdata (Integer userid) {
-        this.fields = new HashMap<>();
         this.userID = userid;
         this.id = -1;
-    }
-
-
-    public void setData (Map<String, Object> map) throws KustvaktException {
-        Set missing = missing(map);
-        if (!missing.isEmpty())
-            throw new KustvaktException(StatusCodes.MISSING_ARGUMENTS,
-                    missing.toString());
-        this.fields.clear();
-        this.fields.putAll(map);
-    }
-
-
-    private Set<String> missing (Map<String, Object> map) {
-        Set<String> missing = new HashSet<>();
-        for (String key : requiredFields()) {
-            if (!map.containsKey(key))
-                missing.add(key);
-        }
-        return missing;
+        this.data = DataFactory.getFactory().convertData(null);
     }
 
 
     public int size () {
-        return this.fields.size();
+        return DataFactory.getFactory().size(this.data);
     }
 
 
+    // fixme: test with json pointer and normal field name
     public Object get (String key) {
-        return this.fields.get(key);
+        return DataFactory.getFactory().getValue(this.data, key);
     }
 
 
@@ -73,82 +54,89 @@
 
 
     public String[] missing () {
-        StringBuilder b = new StringBuilder();
-        Set<String> m = missing(this.fields);
-
-        if (m.isEmpty())
-            return new String[0];
-
-        for (String k : m) {
-            b.append(k).append(";");
+        Set<String> missing = new HashSet<>();
+        Set<String> keys = DataFactory.getFactory().keys(this.data);
+        for (String key : requiredFields()) {
+            if (!keys.contains(key))
+                missing.add(key);
         }
-        return b.toString().split(";");
+        return missing.toArray(new String[0]);
     }
 
 
     public void checkRequired () throws KustvaktException {
         if (!isValid()) {
             String[] fields = missing();
-            throw new KustvaktException(StatusCodes.MISSING_ARGUMENTS,
-                    "User data object not valid. Missing fields: "
-                            + Arrays.asList(fields));
+            throw new KustvaktException(userID, StatusCodes.MISSING_ARGUMENTS,
+                    "User data object not valid. Object has missing fields!",
+                    Arrays.asList(fields).toString());
         }
     }
 
 
+    //fixme: if data array, return empty?!
     public Set<String> keys () {
-        return this.fields.keySet();
+        return DataFactory.getFactory().keys(this.data);
     }
 
 
     public Collection<Object> values () {
-        return this.fields.values();
-    }
-
-
-    public Map<String, Object> fields () {
-        return new HashMap<>(this.fields);
+        return DataFactory.getFactory().values(this.data);
     }
 
 
     public void setData (String data) {
-        Map m = JsonUtils.readSimple(data, Map.class);
-        if (m != null)
-            this.fields.putAll(m);
+        this.data = DataFactory.getFactory().convertData(data);
     }
 
 
     public void update (Userdata other) {
         if (other != null && this.getClass().equals(other.getClass()))
-            this.fields.putAll(other.fields);
+            this.data = DataFactory.getFactory().merge(this.data, other.data);
     }
 
 
-    public String data () {
-        return JsonUtils.toJSON(this.fields);
+    public String serialize () throws KustvaktException {
+        // to have consistency with required fields --> updates/deletion may cause required fields to be missing.
+        this.checkRequired();
+        return DataFactory.getFactory().toStringValue(this.data);
     }
 
 
     public void setField (String key, Object value) {
-        this.fields.put(key, value);
+        DataFactory.getFactory().addValue(this.data, key, value);
     }
 
 
+    // todo:
     public void validate (EncryptionIface crypto) throws KustvaktException {
-        this.fields = crypto.validateMap(this.fields);
+        //this.fields = crypto.validateMap(this.fields);
     }
 
 
-    public void readDefaults (Map<String, Object> map) throws KustvaktException {
-        for (String k : defaultFields()) {
-            Object o = map.get(k);
-            if (o != null)
-                this.fields.put(k, o);
-        }
+    public void read (Map<String, Object> map, boolean defaults_only)
+            throws KustvaktException {
+        this.readQuietly(map, defaults_only);
         this.checkRequired();
     }
 
 
+    public void readQuietly (Map<String, Object> map, boolean defaults_only) {
+        if (defaults_only) {
+            for (String k : defaultFields()) {
+                Object o = map.get(k);
+                if (o != null) {
+                    DataFactory.getFactory().addValue(this.data, k, o);
+                }
+            }
+        }
+        else {
+            for (String key : map.keySet())
+                DataFactory.getFactory().addValue(this.data, key, map.get(key));
+        }
+    }
+
+
     public abstract String[] requiredFields ();
 
 
diff --git a/src/main/java/de/ids_mannheim/korap/utils/UserPropertyReader.java b/src/main/java/de/ids_mannheim/korap/utils/UserPropertyReader.java
index 8c94ee9..df9a1c4 100644
--- a/src/main/java/de/ids_mannheim/korap/utils/UserPropertyReader.java
+++ b/src/main/java/de/ids_mannheim/korap/utils/UserPropertyReader.java
@@ -108,11 +108,11 @@
             //            user.setURIExpiration(0L);
             iface.createAccount(user);
             UserDetails det = new UserDetails(user.getId());
-            det.readDefaults(vals);
+            det.read(vals, true);
             det.validate(crypto);
 
             Userdata set = new UserSettings(user.getId());
-            set.readDefaults(vals);
+            set.read(vals, true);
             set.validate(crypto);
 
             UserDataDbIface dao = BeansFactory.getTypeFactory()
diff --git a/src/main/java/de/ids_mannheim/korap/web/service/full/OAuthService.java b/src/main/java/de/ids_mannheim/korap/web/service/full/OAuthService.java
index 8f11812..82bc5ab 100644
--- a/src/main/java/de/ids_mannheim/korap/web/service/full/OAuthService.java
+++ b/src/main/java/de/ids_mannheim/korap/web/service/full/OAuthService.java
@@ -126,12 +126,10 @@
     public Response getStatus (@Context SecurityContext context,
             @QueryParam("scope") String scopes) {
         TokenContext ctx = (TokenContext) context.getUserPrincipal();
-        Map<String, Object> details;
+        Userdata data;
         try {
             User user = this.controller.getUser(ctx.getUsername());
-            Userdata data = this.controller
-                    .getUserData(user, UserDetails.class);
-            details = data.fields();
+            data = this.controller.getUserData(user, UserDetails.class);
             Set<String> base_scope = StringUtils.toSet(scopes, " ");
             base_scope.retainAll(StringUtils.toSet(scopes));
             scopes = StringUtils.toString(base_scope);
@@ -141,7 +139,7 @@
         }
         // json format with scope callback parameter
         // todo: add other scopes as well!
-        return Response.ok(JsonUtils.toJSON(Scopes.mapScopes(scopes, details)))
+        return Response.ok(JsonUtils.toJSON(Scopes.mapScopes(scopes, data)))
                 .build();
     }
 
diff --git a/src/main/java/de/ids_mannheim/korap/web/service/full/ResourceService.java b/src/main/java/de/ids_mannheim/korap/web/service/full/ResourceService.java
index 8c8a599..a3585af 100644
--- a/src/main/java/de/ids_mannheim/korap/web/service/full/ResourceService.java
+++ b/src/main/java/de/ids_mannheim/korap/web/service/full/ResourceService.java
@@ -32,6 +32,7 @@
 import de.ids_mannheim.korap.web.utils.KustvaktResponseHandler;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Bean;
 
 import javax.ws.rs.*;
 import javax.ws.rs.core.*;
diff --git a/src/main/java/de/ids_mannheim/korap/web/service/full/UserService.java b/src/main/java/de/ids_mannheim/korap/web/service/full/UserService.java
index a0b2846..6750073 100644
--- a/src/main/java/de/ids_mannheim/korap/web/service/full/UserService.java
+++ b/src/main/java/de/ids_mannheim/korap/web/service/full/UserService.java
@@ -227,7 +227,7 @@
             if (scopes != null)
                 base_scope.retainAll(StringUtils.toSet(scopes));
             scopes = StringUtils.toString(base_scope);
-            m = Scopes.mapScopes(scopes, data.fields());
+            m = Scopes.mapScopes(scopes, data);
         }
         catch (KustvaktException e) {
             throw KustvaktResponseHandler.throwit(e);
@@ -248,7 +248,7 @@
             User user = controller.getUser(ctx.getUsername());
             Userdata data = controller.getUserData(user, UserSettings.class);
             data.setField(Attributes.USERNAME, ctx.getUsername());
-            result = data.data();
+            result = data.serialize();
         }
         catch (KustvaktException e) {
             jlog.error("Exception encountered!", e);
@@ -282,9 +282,8 @@
             //            SecurityManager.findbyId(us.getDefaultPOSfoundry(), user, Foundry.class);
             //            SecurityManager.findbyId(us.getDefaultRelfoundry(), user, Foundry.class);
             Userdata new_data = new UserSettings(user.getId());
-            new_data.setData(JsonUtils.toJSON(settings));
+            new_data.readQuietly(settings, false);
             data.update(new_data);
-
             controller.updateUserData(data);
         }
         catch (KustvaktException e) {
@@ -301,14 +300,17 @@
     @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
             PiwikFilter.class, BlockingFilter.class })
     public Response getDetails (@Context SecurityContext context,
-            @Context Locale locale) {
+            @Context Locale locale, @QueryParam("pointer") String pointer) {
         TokenContext ctx = (TokenContext) context.getUserPrincipal();
         String result;
         try {
             User user = controller.getUser(ctx.getUsername());
             Userdata data = controller.getUserData(user, UserDetails.class);
             data.setField(Attributes.USERNAME, ctx.getUsername());
-            result = data.data();
+            if (pointer != null)
+                result = data.get(pointer).toString();
+            else
+                result = data.serialize();
         }
         catch (KustvaktException e) {
             jlog.error("Exception encountered: {}", e.string());
@@ -327,7 +329,7 @@
             @Context Locale locale, MultivaluedMap form) {
         TokenContext ctx = (TokenContext) context.getUserPrincipal();
 
-        Map<String, Object> wrapper = FormRequestWrapper.toMap(form, true);
+        Map<String, Object> new_details = FormRequestWrapper.toMap(form, true);
 
         try {
             User user = controller.getUser(ctx.getUsername());
@@ -335,7 +337,7 @@
                 return Response.notModified().build();
 
             UserDetails new_data = new UserDetails(user.getId());
-            new_data.setData(JsonUtils.toJSON(wrapper));
+            new_data.readQuietly(new_details, false);
 
             UserDetails det = controller.getUserData(user, UserDetails.class);
             det.update(new_data);
diff --git a/src/test/java/de/ids_mannheim/korap/config/BeanConfigTest.java b/src/test/java/de/ids_mannheim/korap/config/BeanConfigTest.java
index 5ddb6dd..0309437 100644
--- a/src/test/java/de/ids_mannheim/korap/config/BeanConfigTest.java
+++ b/src/test/java/de/ids_mannheim/korap/config/BeanConfigTest.java
@@ -18,6 +18,7 @@
 import javax.annotation.PostConstruct;
 
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 
 /**
  * @author hanl
@@ -40,8 +41,9 @@
         assertNotNull("Application context must not be null!", this.context);
         jlog.debug("running one-time before init for class "
                 + this.getClass().getSimpleName() + " ...");
-        initMethod();
         BeansFactory.setKustvaktContext(helper().getContext());
+        assertNotNull(BeansFactory.getKustvaktContext());
+        initMethod();
     }
 
 
diff --git a/src/test/java/de/ids_mannheim/korap/security/ResourceFinderTest.java b/src/test/java/de/ids_mannheim/korap/security/ResourceFinderTest.java
index 24922e6..5da39a4 100644
--- a/src/test/java/de/ids_mannheim/korap/security/ResourceFinderTest.java
+++ b/src/test/java/de/ids_mannheim/korap/security/ResourceFinderTest.java
@@ -31,11 +31,24 @@
     @Test
     public void searchResourcesDemo () throws KustvaktException {
         Set<Corpus> resources = ResourceFinder.searchPublic(Corpus.class);
-        assertFalse(resources.isEmpty());
         assertNotEquals(0, resources.size());
     }
 
 
+    @Test
+    public void testResourcesDemoFiltered () throws KustvaktException {
+        Set<Corpus> resources = ResourceFinder.searchPublicFiltered(
+                Corpus.class, "WPD");
+        assertNotEquals(0, resources.size());
+        assertEquals(1, resources.size());
+
+        resources = ResourceFinder.searchPublicFiltered(Corpus.class, "WPD",
+                "GOE");
+        assertNotEquals(0, resources.size());
+        assertEquals(2, resources.size());
+    }
+
+
     @Override
     public void initMethod () throws KustvaktException {
         helper().setupAccount();
diff --git a/src/test/java/de/ids_mannheim/korap/user/UserdataTest.java b/src/test/java/de/ids_mannheim/korap/user/UserdataTest.java
index d7a3bf4..072f702 100644
--- a/src/test/java/de/ids_mannheim/korap/user/UserdataTest.java
+++ b/src/test/java/de/ids_mannheim/korap/user/UserdataTest.java
@@ -1,5 +1,7 @@
 package de.ids_mannheim.korap.user;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
 import de.ids_mannheim.korap.config.Attributes;
 import de.ids_mannheim.korap.config.BeanConfigTest;
 import de.ids_mannheim.korap.config.BeansFactory;
@@ -8,9 +10,14 @@
 import de.ids_mannheim.korap.handlers.UserDetailsDao;
 import de.ids_mannheim.korap.handlers.UserSettingsDao;
 import de.ids_mannheim.korap.interfaces.db.UserDataDbIface;
+import de.ids_mannheim.korap.utils.JsonUtils;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
+import java.util.HashMap;
+import java.util.Map;
+
 import static org.junit.Assert.*;
 
 /**
@@ -31,27 +38,36 @@
 
 
     @Test
-    public void testDataStore () {
+    public void testDataStore () throws KustvaktException {
         String val = "value1;value_data";
         User user = new KorAPUser();
         user.setId(1);
         UserDetailsDao dao = new UserDetailsDao(helper().getContext()
                 .getPersistenceClient());
         UserDetails d = new UserDetails(1);
+        d.setField(Attributes.FIRSTNAME, "first");
+        d.setField(Attributes.LASTNAME, "last");
+        d.setField(Attributes.ADDRESS, "address");
+        d.setField(Attributes.EMAIL, "email");
         d.setField("key_1", val);
         assertNotEquals(-1, dao.store(d));
     }
 
 
     @Test
-    public void testDataGet () throws dbException {
+    public void testDataGet () throws KustvaktException {
         String val = "value1;value_data";
         User user = new KorAPUser();
         user.setId(1);
         UserDetailsDao dao = new UserDetailsDao(helper().getContext()
                 .getPersistenceClient());
         UserDetails d = new UserDetails(1);
+        d.setField(Attributes.FIRSTNAME, "first");
+        d.setField(Attributes.LASTNAME, "last");
+        d.setField(Attributes.ADDRESS, "address");
+        d.setField(Attributes.EMAIL, "email");
         d.setField("key_1", val);
+
         assertNotEquals(-1, dao.store(d));
 
         d = dao.get(d.getId());
@@ -91,7 +107,7 @@
 
 
     @Test
-    public void testUserdatafactory () throws KustvaktException {
+    public void testUserdataDaoTypefactory () throws KustvaktException {
         UserDataDbIface dao = BeansFactory.getTypeFactory()
                 .getTypeInterfaceBean(
                         helper().getContext().getUserDataProviders(),
@@ -104,7 +120,271 @@
                 UserSettings.class);
         assertNotNull(dao);
         assertEquals(UserSettingsDao.class, dao.getClass());
+    }
 
+
+    @Test
+    public void testDataFactoryAdd () {
+        String data = "{}";
+        Object node = JsonUtils.readTree(data);
+
+        DataFactory factory = DataFactory.getFactory();
+        assertTrue(factory.addValue(node, "field_1", "value_1"));
+        assertTrue(factory.addValue(node, "field_2", 20));
+        assertTrue(factory.addValue(node, "field_3", true));
+
+
+        data = "[]";
+        node = JsonUtils.readTree(data);
+
+        factory = DataFactory.getFactory();
+        assertTrue(factory.addValue(node, "field_1", "value_1"));
+        assertTrue(factory.addValue(node, "field_2", 20));
+        assertTrue(factory.addValue(node, "field_3", true));
+
+    }
+
+
+    @Test
+    public void testDataFactoryGet () {
+        String data = "{}";
+        Object node = JsonUtils.readTree(data);
+
+        DataFactory factory = DataFactory.getFactory();
+        assertTrue(factory.addValue(node, "field_1", "value_1"));
+        assertTrue(factory.addValue(node, "field_2", 20));
+        assertTrue(factory.addValue(node, "field_3", true));
+
+        Object value = factory.getValue(node, "field_1");
+        assertEquals("value_1", value);
+        value = factory.getValue(node, "field_2");
+        assertEquals(20, value);
+        value = factory.getValue(node, "field_3");
+        assertEquals(true, value);
+
+        data = "[]";
+        node = JsonUtils.readTree(data);
+
+        assertTrue(factory.addValue(node, "", "value_2"));
+        assertTrue(factory.addValue(node, "", 10));
+        assertTrue(factory.addValue(node, "", false));
+
+        value = factory.getValue(node, "/0");
+        assertEquals("value_2", value);
+        value = factory.getValue(node, "/1");
+        assertEquals(10, value);
+        value = factory.getValue(node, "/2");
+        assertEquals(false, value);
+    }
+
+
+    @Test
+    public void testUserDataUpdate () {
+        UserDetails details = new UserDetails(-1);
+        details.setField(Attributes.FIRSTNAME, "first");
+        details.setField(Attributes.LASTNAME, "last");
+        details.setField(Attributes.ADDRESS, "address");
+        details.setField(Attributes.EMAIL, "email");
+
+        UserDetails details2 = new UserDetails(-1);
+        details2.setField(Attributes.COUNTRY, "Germany");
+        details.update(details2);
+
+        assertEquals("first", details.get(Attributes.FIRSTNAME));
+        assertEquals("Germany", details.get(Attributes.COUNTRY));
+    }
+
+
+    @Test
+    public void testDataFactoryEmbeddedProperty () {
+        String data = "{}";
+        JsonNode node = JsonUtils.readTree(data);
+
+        DataFactory factory = DataFactory.getFactory();
+        assertTrue(factory.addValue(node, "field_1", "value_1"));
+        assertTrue(factory.addValue(node, "field_2", 20));
+        assertTrue(factory.addValue(node, "field_3", true));
+
+        ArrayNode array = JsonUtils.createArrayNode();
+        array.add(10);
+        array.add("v1");
+        array.add("v2");
+        factory.addValue(node, "field_3", array);
+
+        assertNotNull(node);
+        assertEquals(10, node.at("/field_3/0").asInt());
+        assertEquals("v1", node.at("/field_3/1").asText());
+        assertEquals("v2", node.at("/field_3/2").asText());
+
+    }
+
+
+    @Test
+    public void testUserDataPointerFunction () {
+        UserDetails details = new UserDetails(-1);
+        Map<String, Object> m = new HashMap<>();
+        m.put(Attributes.FIRSTNAME, "first");
+        m.put(Attributes.LASTNAME, "last");
+        m.put(Attributes.ADDRESS, "address");
+        m.put(Attributes.EMAIL, "email");
+        details.setData(JsonUtils.toJSON(m));
+
+        ArrayNode array = JsonUtils.createArrayNode();
+        array.add(100);
+        array.add("message");
+        details.setField("errors", array);
+
+        assertEquals(100, details.get("/errors/0"));
+        assertEquals("message", details.get("/errors/1"));
+    }
+
+
+    @Test
+    public void testDataFactoryMerge () {
+        String data = "{}";
+        Object node = JsonUtils.readTree(data);
+
+        DataFactory factory = DataFactory.getFactory();
+        assertTrue(factory.addValue(node, "field_1", "value_1"));
+        assertTrue(factory.addValue(node, "field_2", 20));
+        assertTrue(factory.addValue(node, "field_3", true));
+
+
+        data = "{}";
+        Object node2 = JsonUtils.readTree(data);
+        assertTrue(factory.addValue(node2, "field_1", "value_new"));
+        assertTrue(factory.addValue(node2, "field_2", "value_next"));
+        assertTrue(factory.addValue(node2, "field_4", "value_2"));
+        assertTrue(factory.addValue(node2, "field_7", "value_3"));
+
+        JsonNode node_new = (JsonNode) factory.merge(node, node2);
+
+        assertEquals("value_new", node_new.path("field_1").asText());
+        assertEquals("value_next", node_new.path("field_2").asText());
+        assertEquals(true, node_new.path("field_3").asBoolean());
+        assertEquals("value_2", node_new.path("field_4").asText());
+        assertEquals("value_3", node_new.path("field_7").asText());
+
+    }
+
+
+    @Test
+    @Ignore
+    public void testDataFactoryRemove () {
+        String data = "{}";
+        Object node = JsonUtils.readTree(data);
+
+        DataFactory factory = DataFactory.getFactory();
+        assertTrue(factory.addValue(node, "field_1", "value_1"));
+        assertTrue(factory.addValue(node, "field_2", 20));
+        assertTrue(factory.addValue(node, "field_3", true));
+
+        Object value = factory.getValue(node, "field_1");
+        assertEquals("value_1", value);
+        value = factory.getValue(node, "field_2");
+        assertEquals(20, value);
+        value = factory.getValue(node, "field_3");
+        assertEquals(true, value);
+
+        assertTrue(factory.removeValue(node, "field_1"));
+        assertTrue(factory.removeValue(node, "field_2"));
+        assertTrue(factory.removeValue(node, "field_3"));
+        assertNotNull(node);
+        assertEquals("{}", node.toString());
+
+        data = "[]";
+        node = JsonUtils.readTree(data);
+
+        assertTrue(factory.addValue(node, "", "value_2"));
+        assertTrue(factory.addValue(node, "", 10));
+        assertTrue(factory.addValue(node, "", false));
+
+        value = factory.getValue(node, "/0");
+        assertEquals("value_2", value);
+        value = factory.getValue(node, "/1");
+        assertEquals(10, value);
+        value = factory.getValue(node, "/2");
+        assertEquals(false, value);
+
+
+        // fixme: cannot be removed
+        assertTrue(factory.removeValue(node, "0"));
+        assertTrue(factory.removeValue(node, "1"));
+        assertTrue(factory.removeValue(node, "2"));
+        assertNotNull(node);
+        assertEquals("[]", node.toString());
+    }
+
+
+    @Test
+    public void testUserdataRequiredFields () {
+        UserDetails details = new UserDetails(-1);
+        Map<String, Object> m = new HashMap<>();
+        m.put(Attributes.FIRSTNAME, "first");
+        m.put(Attributes.LASTNAME, "last");
+        m.put(Attributes.ADDRESS, "address");
+        m.put(Attributes.EMAIL, "email");
+        details.setData(JsonUtils.toJSON(m));
+
+        details.setData(JsonUtils.toJSON(m));
+        String[] missing = details.missing();
+        assertEquals(0, missing.length);
+    }
+
+
+    @Test
+    public void testUserdataDefaultFields () throws KustvaktException {
+        UserSettings settings = new UserSettings(-1);
+        Map<String, Object> m = new HashMap<>();
+        m.put(Attributes.DEFAULT_REL_FOUNDRY, "rel_1");
+        m.put(Attributes.DEFAULT_CONST_FOUNDRY, "const_1");
+        m.put(Attributes.DEFAULT_POS_FOUNDRY, "pos_1");
+        m.put(Attributes.DEFAULT_LEMMA_FOUNDRY, "lemma_1");
+        m.put(Attributes.PAGE_LENGTH, 10);
+        m.put(Attributes.QUERY_LANGUAGE, "poliqarp");
+        m.put(Attributes.METADATA_QUERY_EXPERT_MODUS, false);
+
+        settings.read(m, true);
+
+        assertNotEquals(m.size(), settings.size());
+        assertEquals(settings.defaultFields().length, settings.size());
+        assertEquals("rel_1", settings.get(Attributes.DEFAULT_REL_FOUNDRY));
+        assertEquals("pos_1", settings.get(Attributes.DEFAULT_POS_FOUNDRY));
+        assertEquals("lemma_1", settings.get(Attributes.DEFAULT_LEMMA_FOUNDRY));
+        assertEquals("const_1", settings.get(Attributes.DEFAULT_CONST_FOUNDRY));
+        assertEquals(10, settings.get(Attributes.PAGE_LENGTH));
+
+    }
+
+
+    @Test(expected = KustvaktException.class)
+    public void testUserDataRequiredFieldsException () throws KustvaktException {
+        UserDetails details = new UserDetails(-1);
+        Map<String, Object> m = new HashMap<>();
+        m.put(Attributes.FIRSTNAME, "first");
+        m.put(Attributes.LASTNAME, "last");
+        m.put(Attributes.ADDRESS, "address");
+
+        details.setData(JsonUtils.toJSON(m));
+        String[] missing = details.missing();
+
+        assertEquals(1, missing.length);
+        assertEquals("email", missing[0]);
+        details.checkRequired();
+    }
+
+
+    @Test
+    public void testDataFactoryKeys () {
+        String data = "{}";
+        Object node = JsonUtils.readTree(data);
+
+        DataFactory factory = DataFactory.getFactory();
+        assertTrue(factory.addValue(node, "field_1", "value_1"));
+        assertTrue(factory.addValue(node, "field_2", 20));
+        assertTrue(factory.addValue(node, "field_3", true));
+        assertEquals(3, factory.size(node));
+        assertEquals(3, factory.keys(node).size());
     }
 
 
diff --git a/src/test/java/de/ids_mannheim/korap/web/service/full/ResourceServiceTest.java b/src/test/java/de/ids_mannheim/korap/web/service/full/ResourceServiceTest.java
index 53c2887..c63280a 100644
--- a/src/test/java/de/ids_mannheim/korap/web/service/full/ResourceServiceTest.java
+++ b/src/test/java/de/ids_mannheim/korap/web/service/full/ResourceServiceTest.java
@@ -66,7 +66,6 @@
 
         JsonNode node = JsonUtils.readTree(response.getEntity(String.class));
         assertNotNull(node);
-        System.out.println(node);
         assertEquals(2, node.at("/collection/operands").size());
         assertEquals("textClass", node.at("/collection/operands/0/key")
                 .asText());
@@ -81,8 +80,8 @@
         ClientResponse response = resource().path(getAPIVersion())
                 .path("search").queryParam("q", "[orth=der]")
                 .queryParam("ql", "poliqarp").get(ClientResponse.class);
-        assertEquals(response.getStatus(),
-                ClientResponse.Status.OK.getStatusCode());
+        assertEquals(ClientResponse.Status.OK.getStatusCode(),
+                response.getStatus());
         String ent = response.getEntity(String.class);
         JsonNode node = JsonUtils.readTree(ent);
         assertNotNull(node);
@@ -96,8 +95,8 @@
                 .path("search").queryParam("q", "[orth=der]")
                 .queryParam("ql", "poliqarp").queryParam("context", "sentence")
                 .get(ClientResponse.class);
-        assertEquals(response.getStatus(),
-                ClientResponse.Status.OK.getStatusCode());
+        assertEquals(ClientResponse.Status.OK.getStatusCode(),
+                response.getStatus());
         String ent = response.getEntity(String.class);
         JsonNode node = JsonUtils.readTree(ent);
         assertNotNull(node);
@@ -114,8 +113,8 @@
 
         ClientResponse response = resource().path(getAPIVersion())
                 .path("search").post(ClientResponse.class, s.toJSON());
-        assertEquals(response.getStatus(),
-                ClientResponse.Status.OK.getStatusCode());
+        assertEquals(ClientResponse.Status.OK.getStatusCode(),
+                response.getStatus());
         String ent = response.getEntity(String.class);
 
         JsonNode node = JsonUtils.readTree(ent);
@@ -132,8 +131,8 @@
 
         ClientResponse response = resource().path(getAPIVersion())
                 .path("search").post(ClientResponse.class, s.toJSON());
-        assertEquals(response.getStatus(),
-                ClientResponse.Status.OK.getStatusCode());
+        assertEquals(ClientResponse.Status.OK.getStatusCode(),
+                response.getStatus());
         String ent = response.getEntity(String.class);