org.entcore.communication.services.impl.DefaultCommunicationService.java Source code

Java tutorial

Introduction

Here is the source code for org.entcore.communication.services.impl.DefaultCommunicationService.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.communication.services.impl;

import fr.wseduc.webutils.Either;
import fr.wseduc.webutils.collections.Joiner;
import org.entcore.common.neo4j.Neo4j;
import org.entcore.common.neo4j.StatementsBuilder;
import org.entcore.communication.services.CommunicationService;
import io.vertx.core.Handler;
import io.vertx.core.eventbus.Message;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.core.logging.Logger;
import io.vertx.core.logging.LoggerFactory;

import java.util.HashSet;
import java.util.Set;

import static org.entcore.common.neo4j.Neo4jResult.*;

public class DefaultCommunicationService implements CommunicationService {

    private final Neo4j neo4j = Neo4j.getInstance();
    private static final Logger log = LoggerFactory.getLogger(DefaultCommunicationService.class);

    @Override
    public void addLink(String startGroupId, String endGroupId, Handler<Either<String, JsonObject>> handler) {
        String query = "MATCH (g1:Group {id : {startGroupId}}), (g2:Group {id : {endGroupId}}) "
                + "SET g1.communiqueWith = coalesce(g1.communiqueWith, []) + {endGroupId} "
                + "CREATE UNIQUE g1-[:COMMUNIQUE { source: 'MANUAL'}]->g2 " + "RETURN COUNT(*) as number ";
        JsonObject params = new JsonObject().put("startGroupId", startGroupId).put("endGroupId", endGroupId);
        neo4j.execute(query, params, validUniqueResultHandler(handler));
    }

    @Override
    public void removeLink(String startGroupId, String endGroupId, Handler<Either<String, JsonObject>> handler) {
        String query = "MATCH (g1:Group {id : {startGroupId}})-[r:COMMUNIQUE]->(g2:Group {id : {endGroupId}}) "
                + "SET g1.communiqueWith = FILTER(gId IN g1.communiqueWith WHERE gId <> {endGroupId}) "
                + "DELETE r " + "RETURN COUNT(*) as number ";
        JsonObject params = new JsonObject().put("startGroupId", startGroupId).put("endGroupId", endGroupId);
        neo4j.execute(query, params, validUniqueResultHandler(handler));
    }

    @Override
    public void addLinkWithUsers(String groupId, Direction direction, Handler<Either<String, JsonObject>> handler) {
        String createRelationship;
        switch (direction) {
        case INCOMING:
            createRelationship = "g<-[:COMMUNIQUE { source: 'MANUAL'}]-u ";
            break;
        case OUTGOING:
            createRelationship = "g-[:COMMUNIQUE { source: 'MANUAL'}]->u ";
            break;
        default:
            createRelationship = "g<-[:COMMUNIQUE { source: 'MANUAL'}]-u, g-[:COMMUNIQUE { source: 'MANUAL'}]->u ";
        }
        String query = "MATCH (g:Group { id : {groupId}}) " + "SET g.users = {direction} " + "WITH g "
                + "MATCH g<-[:IN]-(u:User) " + "CREATE UNIQUE " + createRelationship + "RETURN COUNT(*) as number ";
        JsonObject params = new JsonObject().put("groupId", groupId).put("direction", direction.name());
        neo4j.execute(query, params, validUniqueResultHandler(handler));
    }

    @Override
    public void removeLinkWithUsers(String groupId, Direction direction,
            Handler<Either<String, JsonObject>> handler) {
        String relationship;
        String set;
        switch (direction) {
        case INCOMING:
            relationship = "g<-[r:COMMUNIQUE]-(u:User) ";
            set = "SET g.users = CASE WHEN g.users = 'INCOMING' THEN null ELSE 'OUTGOING' END ";
            break;
        case OUTGOING:
            relationship = "g-[r:COMMUNIQUE]->(u:User) ";
            set = "SET g.users = CASE WHEN g.users = 'OUTGOING' THEN null ELSE 'INCOMING' END ";
            break;
        default:
            relationship = "g-[r:COMMUNIQUE]-(u:User) ";
            set = "REMOVE g.users ";
        }
        String query = "MATCH (g:Group { id : {groupId}}) " + set + "WITH g " + "MATCH " + relationship
                + "DELETE r " + "RETURN COUNT(*) as number ";
        JsonObject params = new JsonObject().put("groupId", groupId);
        neo4j.execute(query, params, validUniqueResultHandler(handler));
    }

    @Override
    public void communiqueWith(String groupId, Handler<Either<String, JsonObject>> handler) {
        //      String optional;
        //      switch (filter) {
        //         case GROUPS:
        //            optional = "OPTIONAL MATCH g-[:COMMUNIQUE]->(vg:Group) ";
        //            break;
        //         case USERS:
        //            optional = "OPTIONAL MATCH g-[:COMMUNIQUE]->(vu:User) ";
        //            break;
        //         default:
        //            optional =
        //                  "OPTIONAL MATCH g-[:COMMUNIQUE]->(vg:Group) " +
        //                  "OPTIONAL MATCH g-[:COMMUNIQUE]->(vu:User) ";
        //
        //      }
        String query = "MATCH (g:Group { id : {groupId}}) " + "OPTIONAL MATCH g-[:COMMUNIQUE]->(g1:Group) "
                + "RETURN g as group, COLLECT(g1) as communiqueWith ";
        JsonObject params = new JsonObject().put("groupId", groupId);
        neo4j.execute(query, params, fullNodeMergeHandler("group", handler, "communiqueWith"));
    }

    @Override
    public void addLinkBetweenRelativeAndStudent(String groupId, Direction direction,
            Handler<Either<String, JsonObject>> handler) {
        String createRelationship;
        switch (direction) {
        case INCOMING:
            createRelationship = "u<-[:COMMUNIQUE_DIRECT]-s ";
            break;
        case OUTGOING:
            createRelationship = "u-[:COMMUNIQUE_DIRECT]->s ";
            break;
        default:
            createRelationship = "u<-[:COMMUNIQUE_DIRECT]-s, u-[:COMMUNIQUE_DIRECT]->s ";
        }
        String query = "MATCH (g:Group { id : {groupId}})<-[:IN]-(u:User)<-[:RELATED]-(s:User) "
                + "SET g.relativeCommuniqueStudent = {direction} " + "CREATE UNIQUE " + createRelationship
                + "RETURN COUNT(*) as number ";
        JsonObject params = new JsonObject().put("groupId", groupId).put("direction", direction.name());
        neo4j.execute(query, params, validUniqueResultHandler(handler));
    }

    @Override
    public void removeLinkBetweenRelativeAndStudent(String groupId, Direction direction,
            Handler<Either<String, JsonObject>> handler) {
        String relationship;
        String set;
        switch (direction) {
        case INCOMING:
            relationship = "g<-[:IN]-(u:User)<-[r:COMMUNIQUE_DIRECT]-(s:User) ";
            set = "SET g.relativeCommuniqueStudent = "
                    + "CASE WHEN g.relativeCommuniqueStudent = 'INCOMING' THEN null ELSE 'OUTGOING' END ";
            break;
        case OUTGOING:
            relationship = "g<-[:IN]-(u:User)-[r:COMMUNIQUE_DIRECT]->(s:User) ";
            set = "SET g.relativeCommuniqueStudent = "
                    + "CASE WHEN g.relativeCommuniqueStudent = 'OUTGOING' THEN null ELSE 'INCOMING' END ";
            break;
        default:
            relationship = "g<-[:IN]-(u:User)-[r:COMMUNIQUE_DIRECT]-(s:User) ";
            set = "REMOVE g.relativeCommuniqueStudent ";
        }
        String query = "MATCH (g:Group { id : {groupId}}) " + "WHERE HAS(g.relativeCommuniqueStudent) " + set
                + "WITH g " + "MATCH " + relationship + "DELETE r " + "RETURN COUNT(*) as number ";
        JsonObject params = new JsonObject().put("groupId", groupId);
        neo4j.execute(query, params, validUniqueResultHandler(handler));
    }

    @Override
    public void initDefaultRules(JsonArray structureIds, JsonObject defaultRules,
            final Handler<Either<String, JsonObject>> handler) {
        final StatementsBuilder s1 = new StatementsBuilder();
        final StatementsBuilder s2 = new StatementsBuilder();
        final StatementsBuilder s3 = new StatementsBuilder();
        s3.add("MATCH (s:Structure)<-[:DEPENDS*1..2]-(g:ProfileGroup) " + "WHERE NOT(HAS(g.communiqueWith)) "
                + "SET g.communiqueWith = [] ")
                .add("MATCH (fg:FunctionGroup) " + "WHERE fg.name ENDS WITH 'AdminLocal' "
                        + "SET fg.users = 'BOTH' ")
                .add("MATCH (ag:FunctionalGroup) " + "SET ag.users = 'BOTH' ");
        for (String attr : defaultRules.fieldNames()) {
            initDefaultRules(structureIds, attr, defaultRules.getJsonObject(attr), s1, s2);
        }
        neo4j.executeTransaction(s1.build(), null, false, new Handler<Message<JsonObject>>() {
            @Override
            public void handle(Message<JsonObject> event) {
                if ("ok".equals(event.body().getString("status"))) {
                    Integer transactionId = event.body().getInteger("transactionId");
                    neo4j.executeTransaction(s2.build(), transactionId, false, new Handler<Message<JsonObject>>() {
                        @Override
                        public void handle(Message<JsonObject> event) {
                            if ("ok".equals(event.body().getString("status"))) {
                                Integer transactionId = event.body().getInteger("transactionId");
                                neo4j.executeTransaction(s3.build(), transactionId, true,
                                        new Handler<Message<JsonObject>>() {
                                            @Override
                                            public void handle(Message<JsonObject> message) {
                                                if ("ok".equals(message.body().getString("status"))) {
                                                    handler.handle(
                                                            new Either.Right<String, JsonObject>(new JsonObject()));
                                                    log.info("Default communication rules initialized.");
                                                } else {
                                                    handler.handle(new Either.Left<String, JsonObject>(
                                                            message.body().getString("message")));
                                                    log.error("Error init default com rules : "
                                                            + message.body().getString("message"));
                                                }
                                            }
                                        });
                            } else {
                                handler.handle(
                                        new Either.Left<String, JsonObject>(event.body().getString("message")));
                                log.error("Error init default com rules : " + event.body().getString("message"));
                            }
                        }
                    });
                } else {
                    handler.handle(new Either.Left<String, JsonObject>(event.body().getString("message")));
                    log.error("Error init default com rules : " + event.body().getString("message"));
                }
            }
        });
    }

    private void initDefaultRules(JsonArray structureIds, String attr, JsonObject defaultRules,
            final StatementsBuilder existingGroups, final StatementsBuilder newGroups) {
        final String[] a = attr.split("\\-");
        final String c = "Class".equals(a[0]) ? "*2" : "";
        String relativeStudent = defaultRules.getString("Relative-Student"); // TODO check type in enum
        if (relativeStudent != null && "Relative".equals(a[1])) {
            String query = "MATCH (s:Structure)<-[:DEPENDS" + c + "]-(cg:ProfileGroup) "
                    + "WHERE s.id IN {structures} AND NOT(HAS(cg.communiqueWith)) " + "AND cg.name =~ {profile} "
                    + "SET cg.relativeCommuniqueStudent = {direction} ";
            JsonObject params = new JsonObject().put("structures", structureIds).put("direction", relativeStudent)
                    .put("profile", "^.*?" + a[1] + "$");
            newGroups.add(query, params);
        }
        String users = defaultRules.getString("users"); // TODO check type in enum
        if (users != null) {
            String query = "MATCH (s:Structure)<-[:DEPENDS" + c + "]-(cg:ProfileGroup) "
                    + "WHERE s.id IN {structures} AND NOT(HAS(cg.communiqueWith)) " + "AND cg.name =~ {profile} "
                    + "SET cg.users = {direction} ";
            JsonObject params = new JsonObject().put("structures", structureIds).put("direction", users)
                    .put("profile", "^.*?" + a[1] + "$");
            newGroups.add(query, params);
        }
        JsonArray communiqueWith = defaultRules.getJsonArray("communiqueWith",
                new fr.wseduc.webutils.collections.JsonArray());
        Set<String> classes = new HashSet<>();
        Set<String> structures = new HashSet<>();
        StringBuilder groupLabelSB = new StringBuilder("g:ProfileGroup");
        for (Object o : communiqueWith) {
            if (!(o instanceof String))
                continue;
            String[] s = ((String) o).split("\\-");
            if ("Class".equals(s[0]) && "Structure".equals(a[0])) {
                log.warn("Invalid default configuration " + attr + "->" + o.toString());
            } else if ("Class".equals(s[0])) {
                if ("HeadTeacher".equals(s[1])) {
                    groupLabelSB.append(" OR g:HTGroup");
                }
                classes.add(s[1]);
            } else {
                if ("Func".equals(s[1]) || "Discipline".equals(s[1])) {
                    groupLabelSB.append(" OR g:FunctionGroup");
                } else if ("HeadTeacher".equals(s[1])) {
                    groupLabelSB.append(" OR g:HTGroup");
                }
                structures.add(s[1]);
            }
        }
        final String groupLabel = groupLabelSB.toString();
        JsonObject params = new JsonObject().put("structures", structureIds).put("profile", "^.*?" + a[1] + "$");
        if (!classes.isEmpty()) {
            String query = "MATCH (s:Structure)<-[:DEPENDS" + c + "]-(cg:ProfileGroup)-[:DEPENDS]->(c:Class) "
                    + "WHERE s.id IN {structures} AND HAS(cg.communiqueWith) AND cg.name =~ {profile} "
                    + "WITH cg, c " + "MATCH c<-[:DEPENDS]-(g) " + "WHERE (" + groupLabel
                    + ") AND NOT(HAS(g.communiqueWith)) AND g.name =~ {otherProfile} "
                    + "SET cg.communiqueWith = FILTER(gId IN cg.communiqueWith WHERE gId <> g.id) + g.id ";
            String query2 = "MATCH (s:Structure)<-[:DEPENDS" + c + "]-(cg:ProfileGroup)-[:DEPENDS]->(c:Class) "
                    + "WHERE s.id IN {structures} AND NOT(HAS(cg.communiqueWith)) AND cg.name =~ {profile} "
                    + "WITH cg, c, s " + "MATCH c<-[:DEPENDS]-(g) " + "WHERE  (" + groupLabel
                    + ") AND g.name =~ {otherProfile} "
                    + "SET cg.communiqueWith = coalesce(cg.communiqueWith, []) + g.id ";
            if (!structures.isEmpty()) {
                query2 += "WITH DISTINCT s, cg " + "MATCH s<-[:DEPENDS]-(sg:ProfileGroup) "
                        + "WHERE sg.name =~ {structureProfile} "
                        + "SET cg.communiqueWith = coalesce(cg.communiqueWith, []) + sg.id ";
            }
            JsonObject p = params.copy();
            p.put("otherProfile", "^.*?(" + Joiner.on("|").join(classes) + ")$");
            p.put("structureProfile", "^.*?(" + Joiner.on("|").join(structures) + ")$");
            existingGroups.add(query, p);
            newGroups.add(query2, p);
        }
        if (!structures.isEmpty() && "Structure".equals(a[0])) {
            String query = "MATCH (s:Structure)<-[:DEPENDS" + c + "]-(cg:ProfileGroup), s<-[:DEPENDS]-(g) "
                    + "WHERE s.id IN {structures} AND HAS(cg.communiqueWith) AND cg.name =~ {profile} " + "AND  ("
                    + groupLabel + ") AND NOT(HAS(g.communiqueWith)) AND g.name =~ {otherProfile} "
                    + "SET cg.communiqueWith = FILTER(gId IN cg.communiqueWith WHERE gId <> g.id) + g.id ";
            String query2 = "MATCH (s:Structure)<-[:DEPENDS" + c + "]-(cg:ProfileGroup), s<-[:DEPENDS]-(g) "
                    + "WHERE s.id IN {structures} AND NOT(HAS(cg.communiqueWith)) AND cg.name =~ {profile} "
                    + "AND (" + groupLabel + ") AND g.name =~ {otherProfile} "
                    + "SET cg.communiqueWith = coalesce(cg.communiqueWith, []) + g.id ";
            params.put("otherProfile", "^.*?(" + Joiner.on("|").join(structures) + ")$");
            existingGroups.add(query, params);
            newGroups.add(query2, params);
        }
    }

    @Override
    public void applyDefaultRules(JsonArray structureIds, Handler<Either<String, JsonObject>> handler) {
        StatementsBuilder s = new StatementsBuilder();
        JsonObject params = new JsonObject().put("structures", structureIds);
        String query = "MATCH (s:Structure)<-[:DEPENDS*1..2]-(g:ProfileGroup) "
                + "WHERE s.id IN {structures} AND HAS(g.communiqueWith) AND LENGTH(g.communiqueWith) <> 0 "
                + "WITH DISTINCT g " + "MATCH (pg:Group) " + "WHERE pg.id IN g.communiqueWith "
                + "MERGE g-[:COMMUNIQUE]->pg ";
        s.add(query, params);
        String usersIncoming = "MATCH (s:Structure)<-[:DEPENDS*1..2]-(g:Group)<-[:IN]-(u:User) "
                + "WHERE s.id IN {structures} AND HAS(g.users) AND (g.users = 'INCOMING' OR g.users = 'BOTH') "
                + "MERGE g<-[:COMMUNIQUE]-u ";
        s.add(usersIncoming, params);
        String usersOutgoing = "MATCH (s:Structure)<-[:DEPENDS*1..2]-(g:Group)<-[:IN]-(u:User) "
                + "WHERE s.id IN {structures} AND HAS(g.users) AND (g.users = 'OUTGOING' OR g.users = 'BOTH') "
                + "MERGE g-[:COMMUNIQUE]->u ";
        s.add(usersOutgoing, params);
        String relativeIncoming = "MATCH (s:Structure)<-[:DEPENDS*1..2]-(g:ProfileGroup)<-[:IN]-(r:User)<-[:RELATED]-(u:User) "
                + "WHERE s.id IN {structures} AND HAS(g.relativeCommuniqueStudent) "
                + "AND (g.relativeCommuniqueStudent = 'INCOMING' OR g.relativeCommuniqueStudent = 'BOTH') "
                + "MERGE r<-[:COMMUNIQUE_DIRECT]-u ";
        s.add(relativeIncoming, params);
        String relativeOutgoing = "MATCH (s:Structure)<-[:DEPENDS*1..2]-(g:ProfileGroup)<-[:IN]-(r:User)<-[:RELATED]-(u:User) "
                + "WHERE s.id IN {structures} AND HAS(g.relativeCommuniqueStudent) "
                + "AND (g.relativeCommuniqueStudent = 'OUTGOING' OR g.relativeCommuniqueStudent = 'BOTH') "
                + "MERGE r-[:COMMUNIQUE_DIRECT]->u ";
        s.add(relativeOutgoing, params);
        String setVisible = "MATCH (s:Structure)<-[:DEPENDS*1..2]-(g:Group)<-[:IN*0..1]-(v), "
                + "v-[:COMMUNIQUE|COMMUNIQUE_DIRECT]-() " + "WHERE s.id IN {structures} AND NOT(v:Visible) "
                + "WITH DISTINCT v " + "SET v:Visible ";
        s.add(setVisible, params);
        String setVisible2 = "MATCH (s:Structure)<-[:DEPENDS]-(g:Group)<-[:COMMUNIQUE]-(), " + "g<-[:DEPENDS]-(v)"
                + "WHERE s.id IN {structures} AND NOT(v:Visible) " + "WITH DISTINCT v " + "SET v:Visible ";
        s.add(setVisible2, params);
        neo4j.executeTransaction(s.build(), null, true, validEmptyHandler(handler));
    }

    @Override
    public void applyRules(String groupId, Handler<Either<String, JsonObject>> handler) {
        StatementsBuilder s = new StatementsBuilder();
        JsonObject params = new JsonObject().put("groupId", groupId);
        String query = "MATCH (g:Group {id : {groupId}}) "
                + "WHERE HAS(g.communiqueWith) AND LENGTH(g.communiqueWith) <> 0 " + "WITH g " + "MATCH (pg:Group) "
                + "WHERE pg.id IN g.communiqueWith " + "MERGE g-[:COMMUNIQUE]->pg ";
        s.add(query, params);
        String usersIncoming = "MATCH (g:Group {id : {groupId}})<-[:IN]-(u:User) "
                + "WHERE HAS(g.users) AND (g.users = 'INCOMING' OR g.users = 'BOTH') "
                + "MERGE g<-[:COMMUNIQUE]-u ";
        s.add(usersIncoming, params);
        String usersOutgoing = "MATCH (g:Group {id : {groupId}})<-[:IN]-(u:User) "
                + "WHERE HAS(g.users) AND (g.users = 'OUTGOING' OR g.users = 'BOTH') "
                + "MERGE g-[:COMMUNIQUE]->u ";
        s.add(usersOutgoing, params);
        String relativeIncoming = "MATCH (g:Group {id : {groupId}})<-[:IN]-(r:User)<-[:RELATED]-(u:User) "
                + "WHERE HAS(g.relativeCommuniqueStudent) "
                + "AND (g.relativeCommuniqueStudent = 'INCOMING' OR g.relativeCommuniqueStudent = 'BOTH') "
                + "MERGE r<-[:COMMUNIQUE_DIRECT]-u ";
        s.add(relativeIncoming, params);
        String relativeOutgoing = "MATCH (g:Group {id : {groupId}})<-[:IN]-(r:User)<-[:RELATED]-(u:User) "
                + "WHERE HAS(g.relativeCommuniqueStudent) "
                + "AND (g.relativeCommuniqueStudent = 'OUTGOING' OR g.relativeCommuniqueStudent = 'BOTH') "
                + "MERGE r-[:COMMUNIQUE_DIRECT]->u ";
        s.add(relativeOutgoing, params);
        String setVisible = "MATCH (g:Group {id : {groupId}})<-[:IN]-(v), "
                + "v-[:COMMUNIQUE|COMMUNIQUE_DIRECT]-() " + "WHERE NOT(v:Visible) " + "WITH DISTINCT v "
                + "SET v:Visible ";
        s.add(setVisible, params);
        neo4j.executeTransaction(s.build(), null, true, validEmptyHandler(handler));
    }

    @Override
    public void removeRules(String structureId, Handler<Either<String, JsonObject>> handler) {
        String query;
        JsonObject params = new JsonObject();
        if (structureId != null && !structureId.trim().isEmpty()) {
            query = "MATCH (s:Structure)<-[:DEPENDS*1..2]-(g:ProfileGroup)-[r:COMMUNIQUE]-() "
                    + "WHERE s.id = {schoolId} "
                    + "OPTIONAl MATCH s<-[:BELONGS]-(c:Class)<-[:DEPENDS]-(pg:ProfileGroup)<-[:IN]"
                    + "-(u:User)-[r1:COMMUNIQUE_DIRECT]->() " + "DELETE r, r1";
            params.put("schoolId", structureId);
        } else {
            query = "MATCH ()-[r:COMMUNIQUE]->() " + "OPTIONAL MATCH ()-[r1:COMMUNIQUE_DIRECT]->() "
                    + "DELETE r, r1 ";
        }
        neo4j.execute(query, params, validEmptyHandler(handler));
    }

    @Override
    public void visibleUsers(String userId, String structureId, JsonArray expectedTypes, boolean itSelf,
            boolean myGroup, boolean profile, String preFilter, String customReturn, JsonObject additionnalParams,
            final Handler<Either<String, JsonArray>> handler) {
        visibleUsers(userId, structureId, expectedTypes, itSelf, myGroup, profile, preFilter, customReturn,
                additionnalParams, null, handler);
    }

    @Override
    public void visibleUsers(String userId, String structureId, JsonArray expectedTypes, boolean itSelf,
            boolean myGroup, boolean profile, String preFilter, String customReturn, JsonObject additionnalParams,
            String userProfile, final Handler<Either<String, JsonArray>> handler) {
        StringBuilder query = new StringBuilder();
        JsonObject params = new JsonObject();
        String condition = itSelf ? "" : "AND m.id <> {userId} ";
        StringBuilder union = null;
        String conditionUnion = itSelf ? "" : "AND m.id <> {userId} ";
        if (structureId != null && !structureId.trim().isEmpty()) {
            query.append("MATCH (n:User)-[:COMMUNIQUE*1..3]->m-[:DEPENDS*1..2]->(s:Structure {id:{schoolId}})"); //TODO manage leaf
            params.put("schoolId", structureId);
        } else {
            String l = (myGroup) ? " (length(p) >= 2 OR m.users <> 'INCOMING')" : " length(p) >= 2";
            query.append(
                    " MATCH p=(n:User)-[:COMMUNIQUE*0..2]->ipg" + "-[:COMMUNIQUE*0..1]->g<-[:DEPENDS*0..1]-m ");
            condition += "AND ((" + l
                    + " AND (length(p) < 3 OR (ipg:Group AND (m:User OR g<-[:DEPENDS]-m) AND length(p) = 3)))) ";
            if (userProfile == null || "Student".equals(userProfile) || "Relative".equals(userProfile)) {
                union = new StringBuilder("MATCH p=(n:User)-[:COMMUNIQUE_DIRECT]->m "
                        + "WHERE n.id = {userId} AND (NOT(HAS(m.blocked)) OR m.blocked = false) ");
            }
        }
        query.append("WHERE n.id = {userId} AND (NOT(HAS(m.blocked)) OR m.blocked = false) ");
        if (preFilter != null) {
            query.append(preFilter);
            if (union != null) {
                union.append(preFilter);
                union.append(conditionUnion);
            }
        }
        query.append(condition);
        if (expectedTypes != null && expectedTypes.size() > 0) {
            query.append("AND (");
            StringBuilder types = new StringBuilder();
            for (Object o : expectedTypes) {
                if (!(o instanceof String))
                    continue;
                String t = (String) o;
                types.append(" OR m:").append(t);
            }
            query.append(types.substring(4)).append(") ");
            if (union != null) {
                union.append("AND (").append(types.substring(4)).append(") ");
            }
        }
        String pcr = " ";
        String pr = "";
        if (profile) {
            query.append(
                    "OPTIONAL MATCH m-[:IN*0..1]->pgp-[:DEPENDS*0..1]->(pg:ProfileGroup)-[:HAS_PROFILE]->(profile:Profile) ");
            pcr = ", profile ";
            pr = "profile.name as type, ";
            if (union != null) {
                union.append(
                        "OPTIONAL MATCH m-[:IN*0..1]->pgp-[:DEPENDS*0..1]->(pg:ProfileGroup)-[:HAS_PROFILE]->(profile:Profile) ");
            }
        }
        if (customReturn != null && !customReturn.trim().isEmpty()) {
            query.append("WITH DISTINCT m as visibles").append(pcr);
            query.append(customReturn);
            if (union != null) {
                union.append("WITH DISTINCT m as visibles").append(pcr);
                union.append(customReturn);
            }
        } else {
            query.append("RETURN distinct m.id as id, m.name as name, "
                    + "m.login as login, m.displayName as username, ").append(pr)
                    .append("m.lastName as lastName, m.firstName as firstName, m.profiles as profiles "
                            + "ORDER BY name, username ");
            if (union != null) {
                union.append("RETURN distinct m.id as id, m.name as name, "
                        + "m.login as login, m.displayName as username, ").append(pr)
                        .append("m.lastName as lastName, m.firstName as firstName, m.profiles as profiles "
                                + "ORDER BY name, username ");
            }
        }
        params.put("userId", userId);
        if (additionnalParams != null) {
            params.mergeIn(additionnalParams);
        }
        String q;
        if (union != null) {
            q = query.append(" union ").append(union.toString()).toString();
        } else {
            q = query.toString();
        }
        neo4j.execute(q, params, validResultHandler(handler));
    }

    @Override
    public void usersCanSeeMe(String userId, Handler<Either<String, JsonArray>> handler) {
        String query = "MATCH p=(n:User)<-[:COMMUNIQUE*0..2]-t<-[r:COMMUNIQUE|COMMUNIQUE_DIRECT]-(m:User) "
                + "WHERE n.id = {userId} AND ((type(r) = 'COMMUNIQUE_DIRECT' AND length(p) = 1) "
                + "XOR (type(r) = 'COMMUNIQUE' AND length(p) >= 2)) AND m.id <> {userId} "
                + "RETURN distinct m.id as id, m.login as login, "
                + "m.displayName as username, HEAD(m.profiles) as type " + "ORDER BY username ";
        JsonObject params = new JsonObject();
        params.put("userId", userId);
        neo4j.execute(query, params, validResultHandler(handler));
    }

    @Override
    public void visibleProfilsGroups(String userId, String customReturn, JsonObject additionnalParams,
            String preFilter, Handler<Either<String, JsonArray>> handler) {
        String r;
        if (customReturn != null && !customReturn.trim().isEmpty()) {
            r = "WITH gp as profileGroup, profile " + customReturn;
        } else {
            r = "RETURN distinct gp.id as id, gp.name as name, profile.name as type, "
                    + "gp.groupDisplayName as groupDisplayName " + "ORDER BY type DESC, name ";
        }
        JsonObject params = (additionnalParams != null) ? additionnalParams : new JsonObject();
        params.put("userId", userId);
        String query = "MATCH p=(n:User)-[:COMMUNIQUE*1..2]->l<-[:DEPENDS*0..1]-(gp:Group) "
                + "WHERE n.id = {userId} AND (length(p) > 1 OR gp.users <> 'INCOMING') "
                + (preFilter != null ? preFilter : "")
                + "OPTIONAL MATCH gp-[:DEPENDS*0..1]->(pg:ProfileGroup)-[:HAS_PROFILE]->(profile:Profile) " + r;
        neo4j.execute(query, params, validResultHandler(handler));
    }

    @Override
    public void visibleManualGroups(String userId, String customReturn, JsonObject additionnalParams,
            Handler<Either<String, JsonArray>> handler) {
        String r;
        if (customReturn != null && !customReturn.trim().isEmpty()) {
            r = "WITH mg as manualGroup " + customReturn;
        } else {
            r = "RETURN distinct mg.id as id, mg.name as name, " + "mg.groupDisplayName as groupDisplayName "
                    + "ORDER BY type DESC, name ";
        }
        JsonObject params = (additionnalParams != null) ? additionnalParams : new JsonObject();
        params.put("userId", userId);
        String query = "MATCH p=(n:User)-[:COMMUNIQUE*1..2]->l<-[:DEPENDS*0..1]-(mg:ManualGroup) "
                + "WHERE n.id = {userId} AND (length(p) > 1 OR mg.users <> 'INCOMING') " + r;
        neo4j.execute(query, params, validResultHandler(handler));
    }

}