org.entcore.directory.controllers.UserBookController.java Source code

Java tutorial

Introduction

Here is the source code for org.entcore.directory.controllers.UserBookController.java

Source

/* Copyright  "Open Digital Education", 2014
 *
 * This program is published by "Open Digital Education".
 * You must indicate the name of the software and the company in any production /contribution
 * using the software and indicate on the home page of the software industry in question,
 * "powered by Open Digital Education" with a reference to the website: https://opendigitaleducation.com/.
 *
 * This program is free software, licensed under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation, version 3 of the License.
 *
 * You can redistribute this application and/or modify it since you respect the terms of the GNU Affero General Public License.
 * If you modify the source code and then use this modified source code in your creation, you must make available the source code of your modifications.
 *
 * You should have received a copy of the GNU Affero General Public License along with the software.
 * If not, please see : <http://www.gnu.org/licenses/>. Full compliance requires reading the terms of this license and following its directives.
    
 *
 */

package org.entcore.directory.controllers;

import static fr.wseduc.webutils.Utils.getOrElse;
import static fr.wseduc.webutils.Utils.isNotEmpty;
import static org.entcore.common.http.response.DefaultResponseHandler.arrayResponseHandler;
import static org.entcore.common.http.response.DefaultResponseHandler.defaultResponseHandler;
import static org.entcore.common.http.response.DefaultResponseHandler.leftToResponse;
import static org.entcore.common.neo4j.Neo4jResult.validUniqueResultHandler;
import static org.entcore.common.user.SessionAttributes.PERSON_ATTRIBUTE;
import static org.entcore.common.user.SessionAttributes.THEME_ATTRIBUTE;

import java.io.File;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.entcore.common.events.EventStore;
import org.entcore.common.events.EventStoreFactory;
import org.entcore.common.http.request.JsonHttpServerRequest;
import org.entcore.common.neo4j.Neo;
import org.entcore.common.neo4j.Neo4jResult;
import org.entcore.common.notification.ConversationNotification;
import org.entcore.common.user.UserInfos;
import org.entcore.common.user.UserUtils;
import org.entcore.common.validation.StringValidation;
import org.entcore.directory.services.SchoolService;
import org.entcore.directory.services.UserBookService;
import org.vertx.java.core.http.RouteMatcher;

import fr.wseduc.bus.BusAddress;
import fr.wseduc.rs.Get;
import fr.wseduc.rs.Put;
import fr.wseduc.security.ActionType;
import fr.wseduc.security.SecuredAction;
import fr.wseduc.webutils.Either;
import fr.wseduc.webutils.I18n;
import fr.wseduc.webutils.Server;
import fr.wseduc.webutils.http.BaseController;
import fr.wseduc.webutils.http.HttpClientUtils;
import io.vertx.core.AsyncResult;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.eventbus.Message;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpClientOptions;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;

public class UserBookController extends BaseController {

    private Neo neo;
    private JsonObject config;
    private JsonObject userBookData;
    private HttpClient client;
    private SchoolService schoolService;
    private UserBookService userBookService;
    private EventStore eventStore;
    private ConversationNotification conversationNotification;

    private enum DirectoryEvent {
        ACCESS
    }

    private static final String ANNUAIRE_MODULE = "Annuaire";
    private Map<String, Map<String, String>> activationWelcomeMessage;

    public void setUserBookService(UserBookService userBookService) {
        this.userBookService = userBookService;
    }

    @Override
    public void init(final Vertx vertx, JsonObject config, RouteMatcher rm,
            Map<String, fr.wseduc.webutils.security.SecuredAction> securedActions) {
        pathPrefix = "/userbook";
        super.init(vertx, config, rm, securedActions);
        this.neo = new Neo(vertx, Server.getEventBus(vertx), log);
        this.config = config;
        userBookData = config.getJsonObject("user-book-data");
        final HttpClientOptions options = new HttpClientOptions().setDefaultHost(config.getString("workspace-url"))
                .setDefaultPort(config.getInteger("workspace-port")).setMaxPoolSize(16).setKeepAlive(false);
        client = vertx.createHttpClient(options);
        getWithRegEx(".*", "proxyDocument");
        eventStore = EventStoreFactory.getFactory().getEventStore(ANNUAIRE_MODULE);
        if (config.getBoolean("activation-welcome-message", false)) {
            activationWelcomeMessage = new HashMap<>();
            String assetsPath = (String) vertx.sharedData().getLocalMap("server").get("assetPath");
            Map<String, String> skins = vertx.sharedData().getLocalMap("skins");
            if (skins != null) {
                activationWelcomeMessage = new HashMap<>();
                for (final Map.Entry<String, String> e : skins.entrySet()) {
                    String path = assetsPath + "/assets/themes/" + e.getValue() + "/template/directory/welcome/";
                    vertx.fileSystem().readDir(path, new Handler<AsyncResult<List<String>>>() {
                        @Override
                        public void handle(AsyncResult<List<String>> event) {
                            if (event.succeeded()) {
                                final Map<String, String> messages = new HashMap<>();
                                activationWelcomeMessage.put(e.getKey(), messages);
                                for (final String file : event.result()) {
                                    vertx.fileSystem().readFile(file, new Handler<AsyncResult<Buffer>>() {
                                        @Override
                                        public void handle(AsyncResult<Buffer> event) {
                                            if (event.succeeded()) {
                                                String filename = file.substring(
                                                        file.lastIndexOf(File.separator) + 1,
                                                        file.lastIndexOf("."));
                                                messages.put(filename, event.result().toString());
                                                if (log.isDebugEnabled()) {
                                                    log.debug("Load welcome message " + file + " as " + filename);
                                                }
                                            }
                                        }
                                    });
                                }
                            }
                        }
                    });
                }
            }
        }
    }

    @Get("/mon-compte")
    @SecuredAction(value = "userbook.authent", type = ActionType.AUTHENTICATED)
    public void monCompte(HttpServerRequest request) {
        renderView(request);
    }

    @Get("/birthday")
    @SecuredAction(value = "userbook.authent", type = ActionType.AUTHENTICATED)
    public void birthday(HttpServerRequest request) {
        renderView(request);
    }

    @Get("/mood")
    @SecuredAction(value = "userbook.authent", type = ActionType.AUTHENTICATED)
    public void mood(HttpServerRequest request) {
        renderView(request);
    }

    @Get("/annuaire")
    @SecuredAction(value = "userbook.authent", type = ActionType.AUTHENTICATED)
    public void annuaire(HttpServerRequest request) {
        renderView(request);
        eventStore.createAndStoreEvent(DirectoryEvent.ACCESS.name(), request);
    }

    @Get("/api/search")
    @SecuredAction(value = "userbook.authent", type = ActionType.AUTHENTICATED)
    public void search(final HttpServerRequest request) {
        String name = request.params().get("name");
        String structure = request.params().get("structure");
        String profile = request.params().get("profile");
        String filter = "";
        JsonObject params = new JsonObject();
        if (name == null || name.trim().isEmpty()) {
            badRequest(request, "empty.name");
            return;
        }
        if (profile != null && !profile.trim().isEmpty()) {
            filter += "AND HEAD(m.profiles) = {profile} ";
            params.put("profile", profile);
        }
        if (structure != null && !structure.trim().isEmpty()) {
            filter += "AND (m)-[:IN]->(:ProfileGroup)-[:DEPENDS]->(:Structure {id: {structureId}}) ";
            params.put("structureId", structure);
        }
        String preFilter = "AND m.displayNameSearchField CONTAINS {search} " + filter;
        params.put("search", StringValidation.sanitize(name));
        String customReturn = "OPTIONAL MATCH visibles-[:USERBOOK]->u "
                + "RETURN distinct visibles.id as id, visibles.displayName as displayName, "
                + "u.mood as mood, u.userid as userId, u.picture as photo, " + "HEAD(visibles.profiles) as type "
                + "ORDER BY displayName";
        UserUtils.findVisibleUsers(eb, request, false, false, preFilter, customReturn, params,
                new Handler<JsonArray>() {
                    @Override
                    public void handle(JsonArray users) {
                        renderJson(request, users);
                    }
                });
    }

    @Get("/api/person")
    @SecuredAction(value = "userbook.authent", type = ActionType.AUTHENTICATED)
    public void person(final HttpServerRequest request) {
        UserUtils.getUserInfos(eb, request, new Handler<UserInfos>() {
            @Override
            public void handle(final UserInfos user) {
                if (user != null) {
                    String hobbyVisibility;
                    String personnalInfos;
                    Map<String, Object> params = new HashMap<>();
                    if (request.params().get("id") == null) {
                        Object person = user.getAttribute(PERSON_ATTRIBUTE);
                        if (person != null) {
                            renderJson(request, new JsonObject(person.toString()));
                            return;
                        }
                        params.put("userId", user.getUserId());
                        hobbyVisibility = "PUBLIC|PRIVE";
                        personnalInfos = "OPTIONAL MATCH u-[r0:SHOW_EMAIL]->() "
                                + "OPTIONAL MATCH u-[r1:SHOW_BIRTHDATE]->() "
                                + "OPTIONAL MATCH u-[r2:SHOW_PHONE]->() " + "OPTIONAL MATCH u-[r3:SHOW_MAIL]->() "
                                + "OPTIONAL MATCH u-[r4:SHOW_HEALTH]->u " + "OPTIONAL MATCH u-[r5:SHOW_MOBILE]->() "
                                + "WITH DISTINCT h, s, c, n, v, u, n2, p, n.address as address, "
                                + "n.email as email, u.health as health, "
                                + "n.homePhone as tel, n.birthDate as birthdate, n.mobile as mobile, "
                                + "COLLECT(distinct [type(r0),type(r1),type(r2),type(r3),type(r4),type(r5)]) as r ";
                    } else {
                        params.put("userId", request.params().get("id"));
                        hobbyVisibility = "PUBLIC";
                        personnalInfos = "OPTIONAL MATCH u-[:SHOW_EMAIL]->e " + "OPTIONAL MATCH u-[:SHOW_MAIL]->a "
                                + "OPTIONAL MATCH u-[:SHOW_PHONE]->ph " + "OPTIONAL MATCH u-[:SHOW_MOBILE]->mo "
                                + "OPTIONAL MATCH u-[:SHOW_BIRTHDATE]->b " + "OPTIONAL MATCH u-[:SHOW_HEALTH]->st "
                                + "WITH h, s, c, n, v, u, n2, p, a.address as address, "
                                + "e.email as email, st.health as health, "
                                + "ph.homePhone as tel, b.birthDate as birthdate, mo.mobile as mobile, "
                                + "COLLECT([]) as r ";
                    }
                    String query = "MATCH (n:User) " + "WHERE n.id = {userId} "
                            + "OPTIONAL MATCH n-[:IN]->(:ProfileGroup)-[:HAS_PROFILE]->(p:Profile) "
                            + "OPTIONAL MATCH n-[:IN]->(:ProfileGroup)-[:DEPENDS]->(s:Structure) "
                            + "OPTIONAL MATCH n-[:IN]->(:ProfileGroup)-[:DEPENDS]->(c:Class)-[:BELONGS]->(s) "
                            + "OPTIONAL MATCH (n)-[:USERBOOK]->(u) " + "OPTIONAL MATCH (u)-[v:" + hobbyVisibility
                            + "]->(h1) " + "OPTIONAL MATCH (n)-[:RELATED]-(n2) "
                            + "WITH DISTINCT h1 as h, s, collect(distinct c.name) as c, n, v, u, n2, p "
                            + personnalInfos
                            + "WITH COLLECT(DISTINCT {name: s.name, id: s.id, classes: c}) as schools, "
                            + "n, u, n2, address, email, health, tel, mobile, birthdate, r,  COLLECT(p.name) as type, "
                            + "COLLECT(DISTINCT {visibility: type(v), category: h.category, values: h.values}) as hobbies "
                            + "RETURN DISTINCT " + "n.id as id," + "n.login as login, "
                            + "n.displayName as displayName," + "type," + "address," + "email, " + "tel, "
                            + "mobile, " + "birthdate, " + "HEAD(r) as visibleInfos, " + "schools, "
                            + "n2.displayName as relatedName, " + "n2.id as relatedId," + "n2.type as relatedType,"
                            + "u.userid as userId," + "u.motto as motto,"
                            + "COALESCE(u.picture, {defaultAvatar}) as photo,"
                            + "COALESCE(u.mood, {defaultMood}) as mood," + "health," + "hobbies";
                    params.put("defaultAvatar", userBookData.getString("default-avatar"));
                    params.put("defaultMood", userBookData.getString("default-mood"));
                    neo.send(query, params, new Handler<Message<JsonObject>>() {
                        @Override
                        public void handle(Message<JsonObject> message) {
                            JsonObject r = message.body();
                            if (request.params().get("id") == null) {
                                UserUtils.addSessionAttribute(eb, user.getUserId(), PERSON_ATTRIBUTE, r.encode(),
                                        null);
                            }
                            renderJson(request, r);
                        }
                    });
                } else {
                    unauthorized(request);
                }
            }
        });
    }

    @Get("/structures")
    @SecuredAction(value = "userbook.my.structures", type = ActionType.AUTHENTICATED)
    public void showStructures(final HttpServerRequest request) {
        UserUtils.getUserInfos(eb, request, new Handler<UserInfos>() {

            @Override
            public void handle(UserInfos user) {
                if (user != null) {
                    schoolService.listByUserId(user.getUserId(), arrayResponseHandler(request));
                } else {
                    unauthorized(request);
                }
            }
        });
    }

    @Get("/structure/:structId")
    @SecuredAction(value = "userbook.structure.classes.personnel", type = ActionType.AUTHENTICATED)
    public void showStructure(final HttpServerRequest request) {
        String structureId = request.params().get("structId");
        String customReturn = "MATCH (s:Structure { id : {structId}})<-[:DEPENDS]-(pg:ProfileGroup)"
                + "-[:HAS_PROFILE]->(p:Profile {name : 'Personnel'}), visibles-[:IN]->pg "
                + "OPTIONAL MATCH visibles-[:USERBOOK]->(u:UserBook) "
                + "RETURN DISTINCT p.name as type, visibles.id as id, "
                + "visibles.displayName as displayName, u.mood as mood, " + "u.picture as photo "
                + "ORDER BY type DESC, displayName ";
        final JsonObject params = new JsonObject().put("structId", structureId);
        UserUtils.findVisibleUsers(eb, request, true, customReturn, params, new Handler<JsonArray>() {

            @Override
            public void handle(final JsonArray personnel) {
                String customReturn = "MATCH profileGroup-[:DEPENDS]->(c:Class)-[:BELONGS]->(s:Structure { id : {structId}}) "
                        + "RETURN collect(distinct {id: c.id, name: c.name, level: c.level}) as classes, "
                        + "collect(distinct {id: profileGroup.id, name: profileGroup.name, groupDisplayName: profileGroup.groupDisplayName }) as profileGroups";
                UserUtils.findVisibleProfilsGroups(eb, request, customReturn, params, new Handler<JsonArray>() {
                    @Override
                    public void handle(final JsonArray classesAndProfileGroups) {
                        String customReturn = "MATCH manualGroup-[:DEPENDS]->(c)-[:BELONGS*0..1]->(s:Structure { id : {structId}}) "
                                + "WHERE ALL(label IN labels(c) WHERE label IN [\"Structure\", \"Class\"]) "
                                + "RETURN DISTINCT manualGroup.id as id, manualGroup.name as name, manualGroup.groupDisplayName as groupDisplayName "
                                + "ORDER BY name ASC ";
                        UserUtils.findVisibleManualGroups(eb, request, customReturn, params,
                                new Handler<JsonArray>() {
                                    @Override
                                    public void handle(JsonArray manualGroups) {
                                        JsonObject result = new JsonObject().put("users", personnel).put("classes",
                                                classesAndProfileGroups.getJsonObject(0).getJsonArray("classes",
                                                        new fr.wseduc.webutils.collections.JsonArray()))
                                                .put("profileGroups",
                                                        classesAndProfileGroups.getJsonObject(0).getJsonArray(
                                                                "profileGroups",
                                                                new fr.wseduc.webutils.collections.JsonArray()))
                                                .put("manualGroups", manualGroups);
                                        renderJson(request, result);
                                    }
                                });

                    }
                });
            }
        });
    }

    @Get("/visible/users/:groupId")
    @SecuredAction(value = "userbook.visible.users.group", type = ActionType.AUTHENTICATED)
    public void visibleUsersGroup(final HttpServerRequest request) {
        String groupId = request.params().get("groupId");
        if (groupId == null || groupId.trim().isEmpty()) {
            badRequest(request, "invalid.groupId");
            return;
        }
        String customReturn = "MATCH (s:Group { id : {groupId}})<-[:IN]-(visibles) "
                + "RETURN DISTINCT HEAD(visibles.profiles) as type, visibles.id as id, "
                + "visibles.displayName as displayName, visibles.login as login "
                + "ORDER BY type DESC, displayName ";
        final JsonObject params = new JsonObject().put("groupId", groupId);
        UserUtils.findVisibleUsers(eb, request, true, false, customReturn, params, new Handler<JsonArray>() {

            @Override
            public void handle(final JsonArray users) {
                renderJson(request, users);
            }
        });
    }

    @Get("/api/class")
    @SecuredAction(value = "userbook.authent", type = ActionType.AUTHENTICATED)
    public void myClass(final HttpServerRequest request) {
        String classId = request.params().get("id");
        String matchClass;
        JsonObject params = new JsonObject();
        if (classId == null || classId.trim().isEmpty()) {
            matchClass = "(n:User {id : {userId}})-[:IN]->(pg:ProfileGroup)-[:DEPENDS]->(c:Class) "
                    + "WITH c, profile, visibles MATCH";
        } else {
            matchClass = "(c:Class {id : {classId}}),";
            params.put("classId", classId);
        }
        String query = "MATCH " + matchClass + " visibles-[:IN]->(:ProfileGroup)-[:DEPENDS]->c "
                + "WHERE profile.name IN ['Student', 'Teacher'] " + "OPTIONAL MATCH visibles-[:USERBOOK]->u "
                + "RETURN distinct profile.name as type, visibles.id as id, "
                + "visibles.displayName as displayName, u.mood as mood, "
                + "u.userid as userId, u.picture as photo " + "ORDER BY type DESC, displayName ";
        UserUtils.findVisibleUsers(eb, request, true, true, query, params, new Handler<JsonArray>() {
            @Override
            public void handle(JsonArray users) {
                renderJson(request, users);
            }
        });
    }

    @Get("/api/edit-userbook-info")
    @SecuredAction(value = "userbook.authent", type = ActionType.AUTHENTICATED)
    public void editUserBookInfo(final HttpServerRequest request) {
        UserUtils.getUserInfos(eb, request, new Handler<UserInfos>() {
            @Override
            public void handle(final UserInfos user) {
                if (user != null) {
                    String prop = request.params().get("prop");
                    if (prop != null && (prop.startsWith("theme") || "userPreferencesBirthdayClass".equals(prop))) {
                        String attr = prop.replaceAll("\\W+", "");
                        String neoRequest = "MATCH (n:User)-[:USERBOOK]->(m:UserBook)" + "WHERE n.id = {id} SET m."
                                + attr + "={value}";
                        Map<String, Object> params = new HashMap<>();
                        params.put("id", user.getUserId());
                        params.put("value", request.params().get("value"));
                        neo.send(neoRequest, params, new Handler<Message<JsonObject>>() {

                            @Override
                            public void handle(Message<JsonObject> res) {
                                renderJson(request, res.body());
                            }
                        });
                        UserUtils.removeSessionAttribute(eb, user.getUserId(), THEME_ATTRIBUTE + getHost(request),
                                null);
                    } else {
                        badRequest(request);
                    }
                } else {
                    unauthorized(request);
                }
            }
        });
    }

    @Get("/api/set-visibility")
    @SecuredAction(value = "userbook.authent", type = ActionType.AUTHENTICATED)
    public void setVisibility(final HttpServerRequest request) {
        UserUtils.getUserInfos(eb, request, new Handler<UserInfos>() {
            @Override
            public void handle(UserInfos user) {
                Map<String, Object> params = new HashMap<>();
                params.put("id", user.getUserId());
                params.put("category", request.params().get("category"));
                String visibility = "PUBLIC".equals(request.params().get("value")) ? "PUBLIC" : "PRIVE";
                UserUtils.removeSessionAttribute(eb, user.getUserId(), PERSON_ATTRIBUTE, null);
                neo.send(
                        "MATCH (n:User)-[:USERBOOK]->(m)-[s]->(p) " + "WHERE n.id = {id} AND p.category={category} "
                                + "DELETE s CREATE (m)-[j:" + visibility + "]->(p) " + "RETURN n,m,j,p",
                        params, request.response());
            }
        });
    }

    @SecuredAction(value = "userbook.authent", type = ActionType.AUTHENTICATED)
    public void proxyDocument(final HttpServerRequest request) {
        String defaultImg = request.params().get("userbook-dimg");
        JsonObject defaultContent = null;
        log.debug(defaultImg);
        if (defaultImg != null) {
            defaultContent = new JsonObject().put("type", "file").put("content", defaultImg);
        }
        HttpClientUtils.proxy(request, client, "\\" + pathPrefix, config.getString("workspace-prefix"),
                defaultContent);
    }

    @SecuredAction("userbook.show.motto.mood")
    public void userBookMottoMood() {
    }

    @SecuredAction("userbook.switch.theme")
    public void userBookSwitchTheme() {
    }

    @BusAddress("activation.ack")
    public void initUserBookNode(final Message<JsonObject> message) {
        JsonObject params = new JsonObject();
        params.put("userId", message.body().getString("userId"));
        params.put("avatar", userBookData.getString("default-avatar"));
        params.put("theme", userBookData.getString("default-theme", ""));
        JsonArray queries = new fr.wseduc.webutils.collections.JsonArray();
        String query = "MERGE (m:UserBook { userid : {userId}}) "
                + "SET m.type = 'USERBOOK', m.picture = {avatar}, m.motto = '', "
                + "m.health = '', m.mood = 'default', m.theme =  {theme} " + "WITH m "
                + "MATCH (n:User {id : {userId}}) " + "CREATE UNIQUE n-[:USERBOOK]->m";
        queries.add(Neo.toJsonObject(query, params));
        String query2 = "MATCH (n:User)-[:USERBOOK]->m " + "WHERE n.id = {userId} "
                + "CREATE m-[:PUBLIC]->(c:Hobby {category: {category}, values: {values}})";
        if (isNotEmpty(message.body().getString("theme"))) {
            String query3 = "MATCH (u:User {id:{userId}}) " + "MERGE (u)-[:PREFERS]->(uac:UserAppConf) "
                    + "SET uac.theme = {theme} ";
            JsonObject paramsTheme = new JsonObject().put("userId", message.body().getString("userId")).put("theme",
                    message.body().getString("theme"));
            queries.add(Neo.toJsonObject(query3, paramsTheme));
        }
        for (Object hobby : userBookData.getJsonArray("hobbies")) {
            JsonObject j = params.copy();
            j.put("category", (String) hobby);
            j.put("values", "");
            queries.add(Neo.toJsonObject(query2, j));
        }
        neo.sendBatch(queries, (Handler<Message<JsonObject>>) null);

        welcomeMessage(message);
    }

    @BusAddress("send.welcome.message")
    public void welcomeMessage(Message<JsonObject> message) {
        if (activationWelcomeMessage != null) {
            final HttpServerRequest request = new JsonHttpServerRequest(message.body().getJsonObject("request"));
            Map<String, String> messages = activationWelcomeMessage.get(getHost(request));
            if (messages != null) {
                String welcomeMessage = messages.get(message.body().getString("profile"));
                if (welcomeMessage == null) {
                    welcomeMessage = messages.get("default");
                }
                if (welcomeMessage != null) {
                    conversationNotification.notify(request, "",
                            new fr.wseduc.webutils.collections.JsonArray().add(message.body().getString("userId")),
                            null,
                            I18n.getInstance().translate("welcome.subject", getHost(request),
                                    I18n.acceptLanguage(request)),
                            welcomeMessage, new Handler<Either<String, JsonObject>>() {

                                @Override
                                public void handle(Either<String, JsonObject> r) {
                                    if (r.isLeft()) {
                                        log.error(r.left().getValue());
                                    }
                                }
                            });
                }
            }
        }
    }

    @BusAddress("userbook.preferences")
    public void getUserPreferences(final Message<JsonObject> message) {
        final HttpServerRequest request = new JsonHttpServerRequest(message.body().getJsonObject("request"));
        final String application = message.body().getString("application");
        final String action = message.body().getString("action");

        if (action == null) {
            log.warn("[@BusAddress](userbook.preferences) Invalid action.");
            message.reply(new JsonObject().put("status", "error").put("message", "Invalid action."));
            return;
        }

        switch (action) {
        case "get.currentuser":
            UserUtils.getUserInfos(eb, request, new Handler<UserInfos>() {
                public void handle(UserInfos user) {
                    getUserPrefs(user, request, application, new Handler<Either<String, JsonObject>>() {
                        public void handle(Either<String, JsonObject> result) {
                            if (result.isLeft()) {
                                message.reply(new JsonObject().put("status", "error").put("message",
                                        result.left().getValue()));
                            } else {
                                message.reply(new JsonObject().put("status", "ok").put("value",
                                        result.right().getValue()));
                            }
                        }
                    });
                }
            });
            break;
        case "get.userlist":
            final JsonArray userIds = message.body().getJsonArray("userIds",
                    new fr.wseduc.webutils.collections.JsonArray());
            String query = "MATCH (u:User) " + message.body().getString("additionalMatch", "")
                    + "WHERE u.id IN {userIds} AND u.activationCode IS NULL "
                    + message.body().getString("additionalWhere", "")
                    + "OPTIONAL MATCH (u)-[:PREFERS]->(uac:UserAppConf)  "
                    + "RETURN COLLECT(DISTINCT {userId: u.id, userMail: u.email, lastDomain: u.lastDomain, preferences: uac"
                    + message.body().getString("additionalCollectFields", "") + "}) AS preferences";
            neo.execute(query, new JsonObject().put("userIds", userIds),
                    Neo4jResult.validResultHandler(new Handler<Either<String, JsonArray>>() {
                        public void handle(Either<String, JsonArray> event) {
                            if (event.isLeft()) {
                                message.reply(new JsonObject().put("status", "error").put("message",
                                        event.left().getValue()));
                                return;
                            }
                            JsonArray results = (event.right().getValue().getJsonObject(0))
                                    .getJsonArray("preferences", new fr.wseduc.webutils.collections.JsonArray());
                            for (Object resultObj : results) {
                                JsonObject result = (JsonObject) resultObj;
                                JsonObject prefs = new JsonObject();
                                try {
                                    prefs = new JsonObject(
                                            getOrElse(
                                                    getOrElse(
                                                            getOrElse(result.getJsonObject("preferences"),
                                                                    new JsonObject(), false).getJsonObject("data"),
                                                            new JsonObject(), false).getString(application),
                                                    "{}", false));
                                } catch (Exception e) {
                                    log.error("UserId [" + result.getString("userId", "")
                                            + "] - Bad application preferences format");
                                }
                                result.put("preferences", prefs);
                            }
                            message.reply(new JsonObject().put("status", "ok").put("results", results));
                        }
                    }));
            break;
        default:
            message.reply(new JsonObject().put("status", "error").put("message", "Invalid action."));
            break;
        }

    }

    @Get("/avatar/:id")
    @SecuredAction(value = "userbook.authent", type = ActionType.AUTHENTICATED)
    public void getAvatar(final HttpServerRequest request) {
        final String id = request.params().get("id");
        String thumbnail = request.params().get("thumbnail");
        String defAVatar = userBookData.getString("default-avatar");
        this.userBookService.getAvatar(id, Optional.ofNullable(thumbnail), defAVatar, request);
    }

    @Get("/person/birthday")
    @SecuredAction(value = "userbook.authent", type = ActionType.AUTHENTICATED)
    public void personBirthday(final HttpServerRequest request) {
        Calendar c = Calendar.getInstance();
        int month = c.get(Calendar.MONTH);
        String[] monthRegex = { "12|01|02", "01|02|03", "02|03|04", "03|04|05", "04|05|06", "05|06|07", "06|07|08",
                "07|08|09", "08|09|10", "09|10|11", "10|11|12", "11|12|01" };
        String query = "MATCH (u:User {id:{userId}})-[:IN]->(:ProfileGroup)-[:DEPENDS]->(c:Class) "
                + "WITH DISTINCT c, profile, visibles " + "MATCH visibles-[:IN]->(:ProfileGroup)-[:DEPENDS]->(c) "
                + "WHERE profile.name = 'Student' AND visibles.birthDate=~{regex} "
                + "RETURN distinct visibles.id as id, visibles.displayName as username, "
                + "visibles.birthDate as birthDate, COLLECT(distinct [c.id, c.name]) as classes ";
        JsonObject params = new JsonObject();
        params.put("regex", "^[0-9]{4}-(" + monthRegex[month] + ")-(3[01]|[12][0-9]|0[1-9])$");
        UserUtils.findVisibleUsers(eb, request, true, true, query, params, new Handler<JsonArray>() {
            @Override
            public void handle(JsonArray users) {
                renderJson(request, users);
            }
        });
    }

    @Get("/api/edit-user-info-visibility")
    @SecuredAction(value = "userbook.authent", type = ActionType.AUTHENTICATED)
    public void editUserInfoVisibility(final HttpServerRequest request) {
        final List<String> infos = Arrays.asList("email", "mail", "phone", "mobile", "birthdate", "health");
        final String info = request.params().get("info");
        if (info == null || !infos.contains(info)) {
            badRequest(request);
            return;
        }
        UserUtils.getUserInfos(eb, request, new Handler<UserInfos>() {
            @Override
            public void handle(UserInfos user) {
                if (user != null) {
                    Map<String, Object> params = new HashMap<>();
                    params.put("id", user.getUserId());
                    String relationship = "SHOW_" + info.toUpperCase();
                    String query = "";
                    if ("public".equals(request.params().get("state"))) {
                        query += "MATCH (n:User)-[:USERBOOK]->u WHERE n.id = {id} ";
                        if ("health".equals(info)) {
                            query += "CREATE u-[r:" + relationship + "]->u ";
                        } else {
                            query += "CREATE u-[r:" + relationship + "]->n ";
                        }
                    } else {
                        query += "MATCH (n:User)-[:USERBOOK]->u-[r:" + relationship + "]->() "
                                + "WHERE n.id = {id} " + "DELETE r";
                    }
                    UserUtils.removeSessionAttribute(eb, user.getUserId(), PERSON_ATTRIBUTE, null);
                    neo.send(query, params, request.response());
                } else {
                    unauthorized(request);
                }
            }
        });
    }

    @Get("/user-preferences")
    @SecuredAction(value = "userbook.preferences", type = ActionType.AUTHENTICATED)
    public void userPreferences(final HttpServerRequest request) {
        UserUtils.getUserInfos(eb, request, new Handler<UserInfos>() {
            @Override
            public void handle(UserInfos user) {
                if (user != null) {
                    Map<String, Object> params = new HashMap<>();
                    params.put("id", user.getUserId());
                    String query = "MATCH (n:User)-[:USERBOOK]->u " + "WHERE n.id = {id} "
                            + "RETURN u.userPreferencesBirthdayClass as userPreferencesBirthdayClass";
                    neo.send(query, params, request.response());
                } else {
                    unauthorized(request);
                }
            }
        });
    }

    @Get("/preference/:application")
    @SecuredAction(value = "user.preference", type = ActionType.AUTHENTICATED)
    public void getPreference(final HttpServerRequest request) {
        UserUtils.getUserInfos(eb, request, new Handler<UserInfos>() {
            @Override
            public void handle(final UserInfos user) {
                if (user != null) {
                    final String application = request.params().get("application").replaceAll("\\W+", "");
                    getUserPrefs(user, request, application, new Handler<Either<String, JsonObject>>() {
                        public void handle(Either<String, JsonObject> event) {
                            if (event.isLeft()) {
                                badRequest(request, event.left().getValue());
                            } else {
                                renderJson(request, event.right().getValue());
                            }
                        }
                    });
                } else {
                    badRequest(request);
                }
            }
        });
    }

    private void getUserPrefs(final UserInfos user, final HttpServerRequest request, final String application,
            final Handler<Either<String, JsonObject>> handler) {
        if (user != null) {
            UserUtils.getSession(eb, request, new Handler<JsonObject>() {
                public void handle(JsonObject session) {
                    final JsonObject cache = session.getJsonObject("cache");

                    if (cache.containsKey("preferences")) {
                        handler.handle(new Either.Right<String, JsonObject>(new JsonObject().put("preference",
                                cache.getJsonObject("preferences").getString(application))));
                    } else {
                        refreshPreferences(user, request, new Handler<Either<String, JsonObject>>() {
                            public void handle(Either<String, JsonObject> event) {
                                if (event.isLeft()) {
                                    log.error(event.left().getValue());
                                    handler.handle(
                                            new Either.Left<String, JsonObject>("refresh.preferences.failed"));
                                } else {
                                    handler.handle(new Either.Right<String, JsonObject>(new JsonObject()
                                            .put("preference", event.right().getValue().getString(application))));
                                }
                            }
                        });
                    }
                }
            });
        } else {
            handler.handle(new Either.Left<String, JsonObject>("bad.user"));
        }
    }

    private void refreshPreferences(final UserInfos user, final HttpServerRequest request,
            final Handler<Either<String, JsonObject>> handler) {
        String query = "MATCH (u:User {id:{userId}})-[:PREFERS]->(uac:UserAppConf)" + " RETURN uac AS preferences";

        neo.execute(query, new JsonObject().put("userId", user.getUserId()),
                Neo4jResult.fullNodeMergeHandler("preferences", new Handler<Either<String, JsonObject>>() {
                    @Override
                    public void handle(final Either<String, JsonObject> result) {
                        if (result.isRight()) {
                            UserUtils.addSessionAttribute(eb, user.getUserId(), "preferences",
                                    result.right().getValue(), new Handler<Boolean>() {
                                        public void handle(Boolean event) {
                                            if (Boolean.FALSE.equals(event)) {
                                                log.error("Could not add preferences attribute to session.");
                                            }
                                        }
                                    });
                        }
                        handler.handle(result);
                    }
                }));
    }

    @Put("/preference/:application")
    @SecuredAction(value = "user.preference", type = ActionType.AUTHENTICATED)
    public void updatePreference(final HttpServerRequest request) {
        UserUtils.getUserInfos(eb, request, new Handler<UserInfos>() {
            @Override
            public void handle(final UserInfos user) {
                if (user != null) {
                    final JsonObject params = new JsonObject().put("userId", user.getUserId());
                    final String application = request.params().get("application").replaceAll("\\W+", "");
                    request.bodyHandler(new Handler<Buffer>() {
                        @Override
                        public void handle(Buffer body) {
                            params.put("conf", body.toString("UTF-8"));
                            String query = "MATCH (u:User {id:{userId}})"
                                    + "MERGE (u)-[:PREFERS]->(uac:UserAppConf)" + " ON CREATE SET uac."
                                    + application + " = {conf}" + " ON MATCH SET uac." + application + " = {conf}";
                            neo.execute(query, params,
                                    validUniqueResultHandler(new Handler<Either<String, JsonObject>>() {
                                        @Override
                                        public void handle(Either<String, JsonObject> result) {
                                            if (result.isRight()) {
                                                renderJson(request, result.right().getValue());

                                                UserUtils.getSession(eb, request, new Handler<JsonObject>() {
                                                    public void handle(JsonObject session) {
                                                        final JsonObject cache = session.getJsonObject("cache");

                                                        if (cache.containsKey("preferences")) {
                                                            JsonObject prefs = cache.getJsonObject("preferences");
                                                            prefs.put(application, params.getString("conf"));
                                                            if ("theme".equals(application)) {
                                                                prefs.remove(THEME_ATTRIBUTE + getHost(request));
                                                            }
                                                            UserUtils.addSessionAttribute(eb, user.getUserId(),
                                                                    "preferences", prefs, new Handler<Boolean>() {
                                                                        public void handle(Boolean event) {
                                                                            UserUtils.removeSessionAttribute(eb,
                                                                                    user.getUserId(),
                                                                                    THEME_ATTRIBUTE
                                                                                            + getHost(request),
                                                                                    null);
                                                                            if (!event)
                                                                                log.error(
                                                                                        "Could not add preferences attribute to session.");
                                                                        }
                                                                    });
                                                        }
                                                    }
                                                });
                                            } else {
                                                leftToResponse(request, result.left());
                                            }
                                        }
                                    }));
                        }
                    });
                } else {
                    badRequest(request);
                }
            }
        });
    }

    @Get("/search/criteria")
    @SecuredAction(value = "", type = ActionType.AUTHENTICATED)
    public void searchCriteria(HttpServerRequest request) {
        UserUtils.getUserInfos(eb, request, user -> {
            if (user != null) {
                schoolService.searchCriteria(user.getStructures(), defaultResponseHandler(request));
            } else {
                badRequest(request, "invalid.user");
            }
        });
    }

    public void setSchoolService(SchoolService schoolService) {
        this.schoolService = schoolService;
    }

    public void setConversationNotification(ConversationNotification conversationNotification) {
        this.conversationNotification = conversationNotification;
    }

}