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);