org.entcore.directory.services.impl.DefaultSchoolService.java Source code

Java tutorial

Introduction

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

import fr.wseduc.webutils.Either;

import io.vertx.core.eventbus.Message;
import org.entcore.common.neo4j.Neo4j;
import org.entcore.common.neo4j.Neo4jResult;
import org.entcore.common.user.UserInfos;
import org.entcore.directory.Directory;
import org.entcore.directory.services.SchoolService;
import io.vertx.core.Handler;
import io.vertx.core.eventbus.EventBus;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;

import java.util.List;

import static fr.wseduc.webutils.Utils.handlerToAsyncHandler;
import static org.entcore.common.neo4j.Neo4jResult.*;
import static org.entcore.common.user.DefaultFunctions.ADMIN_LOCAL;
import static org.entcore.common.user.DefaultFunctions.CLASS_ADMIN;
import static org.entcore.common.user.DefaultFunctions.SUPER_ADMIN;

public class DefaultSchoolService implements SchoolService {

    private final Neo4j neo = Neo4j.getInstance();
    private final EventBus eventBus;

    public DefaultSchoolService(EventBus eventBus) {
        this.eventBus = eventBus;
    }

    @Override
    public void create(JsonObject school, Handler<Either<String, JsonObject>> result) {
        JsonObject action = new JsonObject().put("action", "manual-create-structure").put("data", school);
        eventBus.send(Directory.FEEDER, action, handlerToAsyncHandler(validUniqueResultHandler(result)));
    }

    @Override
    public void get(String id, Handler<Either<String, JsonObject>> result) {
        String query = "match (s:`Structure`) where s.id = {id} return s.id as id, s.UAI as UAI, s.name as name";
        neo.execute(query, new JsonObject().put("id", id), validUniqueResultHandler(result));
    }

    @Override
    public void getByClassId(String classId, Handler<Either<String, JsonObject>> result) {
        String query = "match (c:`Class` {id : {id}})-[:BELONGS]->(s:`Structure`) "
                + "return s.id as id, s.UAI as UAI, s.name as name, s.externalId as externalId ";
        neo.execute(query, new JsonObject().put("id", classId), validUniqueResultHandler(result));
    }

    @Override
    public void listByUserId(String userId, Handler<Either<String, JsonArray>> results) {
        String query = "MATCH (u:User { id: {id}})-[:IN]->(g: Group)-[:DEPENDS]->(s:Structure) "
                + "OPTIONAL MATCH (s)-[r:HAS_ATTACHMENT]->(ps:Structure) "
                + "WITH s, COLLECT(DISTINCT {id: ps.id, name: ps.name}) as parents "
                + "RETURN DISTINCT s.id as id, s.UAI as UAI, s.name as name, "
                + "CASE WHEN any(p in parents where p <> {id: null, name: null}) THEN parents END as parents";
        neo.execute(query, new JsonObject().put("id", userId), validResultHandler(results));
    }

    @Override
    public void listAdmin(UserInfos userInfos, Handler<Either<String, JsonArray>> results) {
        if (userInfos == null) {
            results.handle(new Either.Left<String, JsonArray>("invalid.user"));
            return;
        }
        String condition = "";
        JsonObject params = new JsonObject();
        if (!userInfos.getFunctions().containsKey(SUPER_ADMIN)
                && !userInfos.getFunctions().containsKey(ADMIN_LOCAL)) {
            results.handle(new Either.Left<String, JsonArray>("forbidden"));
            return;
        } else if (!userInfos.getFunctions().containsKey(SUPER_ADMIN)
                && userInfos.getFunctions().containsKey(ADMIN_LOCAL)) {
            UserInfos.Function f = userInfos.getFunctions().get(ADMIN_LOCAL);
            List<String> scope = f.getScope();
            if (scope != null && !scope.isEmpty()) {
                condition = "WHERE s.id IN {structures} ";
                params.put("structures", new fr.wseduc.webutils.collections.JsonArray(scope));
            }
        }
        String query = "MATCH (s:Structure) " + condition + "OPTIONAL MATCH (s)-[r:HAS_ATTACHMENT]->(ps:Structure) "
                + "WITH s, COLLECT({id: ps.id, name: ps.name}) as parents "
                + "RETURN s.id as id, s.UAI as UAI, s.name as name, s.externalId as externalId, s.timetable as timetable, "
                + "CASE WHEN any(p in parents where p <> {id: null, name: null}) THEN parents END as parents";

        neo.execute(query, params, validResultHandler(results));
    }

    @Override
    public void link(String structureId, String userId, Handler<Either<String, JsonObject>> result) {
        JsonObject action = new JsonObject().put("action", "manual-add-user").put("structureId", structureId)
                .put("userId", userId);
        eventBus.send(Directory.FEEDER, action, handlerToAsyncHandler(validUniqueResultHandler(result)));
    }

    @Override
    public void unlink(String structureId, String userId, Handler<Either<String, JsonObject>> result) {
        JsonObject action = new JsonObject().put("action", "manual-remove-user").put("structureId", structureId)
                .put("userId", userId);
        eventBus.send(Directory.FEEDER, action, handlerToAsyncHandler(validUniqueResultHandler(result)));
    }

    @Override
    public void defineParent(String structureId, String parentStructureId,
            Handler<Either<String, JsonObject>> handler) {
        JsonObject action = new JsonObject().put("action", "manual-structure-attachment")
                .put("structureId", structureId).put("parentStructureId", parentStructureId);
        eventBus.send(Directory.FEEDER, action, handlerToAsyncHandler(validUniqueResultHandler(0, handler)));
    }

    @Override
    public void removeParent(String structureId, String parentStructureId,
            Handler<Either<String, JsonObject>> handler) {
        JsonObject action = new JsonObject().put("action", "manual-structure-detachment")
                .put("structureId", structureId).put("parentStructureId", parentStructureId);
        eventBus.send(Directory.FEEDER, action, handlerToAsyncHandler(validUniqueResultHandler(handler)));
    }

    @Override
    public void list(JsonArray fields, Handler<Either<String, JsonArray>> results) {
        if (fields == null || fields.size() == 0) {
            fields = new fr.wseduc.webutils.collections.JsonArray().add("id").add("externalId").add("name")
                    .add("UAI");
        }
        StringBuilder query = new StringBuilder();
        query.append("MATCH (s:Structure) RETURN ");
        for (Object field : fields) {
            query.append(" s.").append(field).append(" as ").append(field).append(",");
        }
        query.deleteCharAt(query.length() - 1);
        neo.execute(query.toString(), (JsonObject) null, validResultHandler(results));
    }

    @Override
    public void update(String structureId, JsonObject body, Handler<Either<String, JsonObject>> result) {
        JsonObject action = new JsonObject().put("action", "manual-update-structure")
                .put("structureId", structureId).put("data", body);
        eventBus.send(Directory.FEEDER, action, handlerToAsyncHandler(validUniqueResultHandler(result)));
    }

    @Override
    public void getLevels(String structureId, UserInfos userInfos, Handler<Either<String, JsonArray>> results) {
        String filter = "MATCH (s:Structure {id: {structureId}})<-[:DEPENDS]-(g:ProfileGroup)<-[:IN]-(u:User) ";
        String condition = "WHERE has(u.level) ";
        String filter2 = "MATCH u-[:IN]->(:ProfileGroup)-[:DEPENDS]->(class:Class)-[:BELONGS]->(n) "
                + "WITH distinct u.level as name, collect(distinct {id: class.id, name: class.name}) as classes "
                + "RETURN distinct name, classes";

        JsonObject params = new JsonObject().put("structureId", structureId);

        //Admin check
        if (!userInfos.getFunctions().containsKey(SUPER_ADMIN) && !userInfos.getFunctions().containsKey(ADMIN_LOCAL)
                && !userInfos.getFunctions().containsKey(CLASS_ADMIN)) {
            results.handle(new Either.Left<String, JsonArray>("forbidden"));
            return;
        } else if (userInfos.getFunctions().containsKey(ADMIN_LOCAL)) {
            UserInfos.Function f = userInfos.getFunctions().get(ADMIN_LOCAL);
            List<String> scope = f.getScope();
            if (scope != null && !scope.isEmpty()) {
                condition += "AND s.id IN {scope} ";
                params.put("scope", new fr.wseduc.webutils.collections.JsonArray(scope));
            }
        } else if (userInfos.getFunctions().containsKey(CLASS_ADMIN)) {
            UserInfos.Function f = userInfos.getFunctions().get(CLASS_ADMIN);
            List<String> scope = f.getScope();
            if (scope != null && !scope.isEmpty()) {
                condition = "AND class.id IN {scope} ";
                params.put("scope", new fr.wseduc.webutils.collections.JsonArray(scope));
            }
        }

        String query = filter + condition + filter2;

        neo.execute(query.toString(), params, validResultHandler(results));
    }

    @Override
    public void massmailUsers(String structureId, JsonObject filterObj, UserInfos userInfos,
            Handler<Either<String, JsonArray>> results) {
        this.massmailUsers(structureId, filterObj, true, true, null, userInfos, results);
    }

    @Override
    public void massmailUsers(String structureId, JsonObject filterObj, boolean groupClasses, boolean groupChildren,
            Boolean hasMail, UserInfos userInfos, Handler<Either<String, JsonArray>> results) {

        String filter = "MATCH (s:Structure {id: {structureId}})<-[:DEPENDS]-(g:ProfileGroup)<-[:IN]-(u:User), "
                + "(g)-[:HAS_PROFILE]-(p: Profile) ";
        String condition = "";
        String optional = "OPTIONAL MATCH (s)<-[:BELONGS]-(c:Class)<-[:DEPENDS]-(:ProfileGroup)<-[:IN]-(u) "
                + "OPTIONAL MATCH (u)<-[:RELATED]-(child: User)-[:IN]->(:ProfileGroup)-[:DEPENDS]->(c) ";

        JsonObject params = new JsonObject().put("structureId", structureId);

        //Activation
        if (filterObj.containsKey("activated")) {
            String activated = filterObj.getString("activated", "false");
            if ("false".equals(activated.toLowerCase())) {
                condition = "WHERE NOT(u.activationCode IS NULL) ";
            } else if ("true".equals(activated.toLowerCase())) {
                condition = "WHERE (u.activationCode IS NULL) ";
            } else {
                condition = "WHERE 1 = 1 ";
            }
        } else {
            condition = "WHERE NOT(u.activationCode IS NULL) ";
        }

        //Profiles
        if (filterObj.getJsonArray("profiles").size() > 0) {
            condition += "AND p.name IN {profilesArray} ";
            params.put("profilesArray", filterObj.getJsonArray("profiles"));
        }

        //Levels
        if (filterObj.getJsonArray("levels").size() > 0) {
            condition += " AND u.level IN {levelsArray} ";
            params.put("levelsArray", filterObj.getJsonArray("levels"));
        }

        //Classes
        if (filterObj.getJsonArray("classes").size() > 0) {
            filter += ", (c:Class)<-[:DEPENDS]-(:ProfileGroup)<-[:IN]-(u) ";
            optional = "OPTIONAL MATCH (u)<-[:RELATED]-(child: User)-[:IN]->(:ProfileGroup)-[:DEPENDS]->(c) ";
            condition += " AND c.id IN {classesArray} ";
            params.put("classesArray", filterObj.getJsonArray("classes"));
        }

        //Email
        if (hasMail != null) {
            if (hasMail) {
                condition += " AND COALESCE(u.email, \"\") <> \"\" ";
            } else {
                condition += " AND COALESCE(u.email, \"\") = \"\" ";
            }

        }

        //Admin check
        if (!userInfos.getFunctions().containsKey(SUPER_ADMIN) && !userInfos.getFunctions().containsKey(ADMIN_LOCAL)
                && !userInfos.getFunctions().containsKey(CLASS_ADMIN)) {
            results.handle(new Either.Left<String, JsonArray>("forbidden"));
            return;
        } else if (userInfos.getFunctions().containsKey(ADMIN_LOCAL)) {
            UserInfos.Function f = userInfos.getFunctions().get(ADMIN_LOCAL);
            List<String> scope = f.getScope();
            if (scope != null && !scope.isEmpty()) {
                condition += "AND s.id IN {scope} ";
                params.put("scope", new fr.wseduc.webutils.collections.JsonArray(scope));
            }
        } else if (userInfos.getFunctions().containsKey(CLASS_ADMIN)) {
            if (filterObj.getJsonArray("classes").size() < 1) {
                results.handle(new Either.Left<String, JsonArray>("forbidden"));
                return;
            }

            UserInfos.Function f = userInfos.getFunctions().get(CLASS_ADMIN);
            List<String> scope = f.getScope();
            if (scope != null && !scope.isEmpty()) {
                condition = "AND c.id IN {scope} ";
                params.put("scope", new fr.wseduc.webutils.collections.JsonArray(scope));
            }
        }

        //With clause
        String withStr = "WITH u, p ";

        //Return clause
        String returnStr = "RETURN distinct collect(p.name)[0] as profile, "
                + "u.id as id, u.firstName as firstName, u.lastName as lastName, "
                + "u.email as email, CASE WHEN u.loginAlias IS NOT NULL THEN u.loginAlias ELSE u.login END as login, u.activationCode as activationCode ";

        if (groupClasses) {
            withStr += ", collect(distinct c.name) as classes, min(c.name) as classname, CASE count(c) WHEN 0 THEN false ELSE true END as isInClass ";
            returnStr += ", classes, classname, isInClass ";
        } else {
            withStr += ", c.name as classname, CASE count(c) WHEN 0 THEN false ELSE true END as isInClass ";
            returnStr += ", classname, isInClass ";
        }

        if (groupChildren) {
            withStr += ", CASE count(child) WHEN 0 THEN null ELSE collect(distinct {firstName: child.firstName, lastName: child.lastName, classname: c.name}) END as children ";
            returnStr += ", filter(c IN children WHERE not(c.firstName is null)) as children ";
        } else {
            if (groupClasses) {
                withStr = "WITH u, p, c, " + "CASE count(child) WHEN 0 THEN null "
                        + "ELSE {firstName: child.firstName, lastName: child.lastName, classname: c.name} "
                        + "END as child " + withStr + ", child ";
            } else {
                withStr += ", CASE count(child) WHEN 0 THEN null ELSE {firstName: child.firstName, lastName: child.lastName } END as child ";
            }
            returnStr += ", child ";
        }

        //Order by
        String sort = "ORDER BY ";
        for (Object sortObj : filterObj.getJsonArray("sort")) {
            String sortstr = (String) sortObj;
            sort += sortstr + ",";
        }
        sort += "lastName";

        String query = filter + condition + optional + withStr + returnStr + sort;

        neo.execute(query.toString(), params, validResultHandler(results));
    }

    public void massMailUser(String userId, UserInfos userInfos, Handler<Either<String, JsonArray>> results) {
        String filter = "MATCH (s:Structure)<-[:DEPENDS]-(g:ProfileGroup)<-[:IN]-(u:User {id: {userId}}), "
                + "(g)-[:HAS_PROFILE]-(p: Profile) ";
        String condition = "";
        String optional = "OPTIONAL MATCH (s)<-[:BELONGS]-(c:Class)<-[:DEPENDS]-(:ProfileGroup)<-[:IN]-(u) "
                + "OPTIONAL MATCH (u)<-[:RELATED]-(child: User)-[:IN]->(:ProfileGroup)-[:DEPENDS]->(c) ";

        JsonObject params = new JsonObject().put("userId", userId);

        //With clause
        String withStr = "WITH u, p ";

        //Return clause
        String returnStr = "RETURN distinct collect(p.name)[0] as profile, "
                + "u.id as id, u.firstName as firstName, u.lastName as lastName, "
                + "u.email as email, CASE WHEN u.loginAlias IS NOT NULL THEN u.loginAlias ELSE u.login END as login, u.activationCode as activationCode ";

        withStr += ", collect(distinct c.name) as classes, min(c.name) as classname, CASE count(c) WHEN 0 THEN false ELSE true END as isInClass ";
        returnStr += ", classes, classname, isInClass ";

        withStr += ", CASE count(child) WHEN 0 THEN null ELSE {firstName: child.firstName, lastName: child.lastName } END as child ";

        String query = filter + condition + optional + withStr + returnStr;

        neo.execute(query.toString(), params, validResultHandler(results));
    }

    @Override
    public void massMailAllUsersByStructure(String structureId, UserInfos userInfos,
            Handler<Either<String, JsonArray>> results) {
        String filter = "MATCH (s:Structure {id: {structureId}})<-[:DEPENDS]-(g:ProfileGroup)<-[:IN]-(u:User), "
                + "(g)-[:HAS_PROFILE]-(p: Profile) ";
        String condition = "";
        String optional = "OPTIONAL MATCH (s)<-[:BELONGS]-(c:Class)<-[:DEPENDS]-(:ProfileGroup)<-[:IN]-(u) "
                + "OPTIONAL MATCH (u)<-[:RELATED]-(child: User)-[:IN]->(:ProfileGroup)-[:DEPENDS]->(c) ";

        JsonObject params = new JsonObject().put("structureId", structureId);

        //Admin check
        if (!userInfos.getFunctions().containsKey(SUPER_ADMIN)
                && !userInfos.getFunctions().containsKey(ADMIN_LOCAL)) {
            results.handle(new Either.Left<String, JsonArray>("forbidden"));
            return;
        } else if (userInfos.getFunctions().containsKey(ADMIN_LOCAL)) {
            UserInfos.Function f = userInfos.getFunctions().get(ADMIN_LOCAL);
            List<String> scope = f.getScope();
            if (scope != null && !scope.isEmpty()) {
                condition += "WHERE s.id IN {scope} ";
                params.put("scope", new fr.wseduc.webutils.collections.JsonArray(scope));
            }
        }

        //With clause
        String withStr = "WITH u, p ";

        //Return clause
        String returnStr = "RETURN distinct collect(p.name)[0] as type, "
                + "u.id as id, u.firstName as firstName, u.lastName as lastName, "
                + "u.email as email, CASE WHEN u.loginAlias IS NOT NULL THEN u.loginAlias ELSE u.login END as login, u.activationCode as code ";

        withStr += ", collect(distinct {id: c.id, name: c.name}) as classes, min(c.name) as classname, CASE count(c) WHEN 0 THEN false ELSE true END as isInClass ";
        returnStr += ", classes, classname, isInClass ";

        withStr += ", CASE count(child) WHEN 0 THEN null ELSE collect(distinct {firstName: child.firstName, lastName: child.lastName, classname: c.name}) END as children ";
        returnStr += ", filter(c IN children WHERE not(c.firstName is null)) as children ";

        String sort = "ORDER BY lastName";

        String query = filter + condition + optional + withStr + returnStr + sort;

        neo.execute(query.toString(), params, validResultHandler(results));
    }

    @Override
    public void listSources(String structureId, Handler<Either<String, JsonArray>> result) {
        String query = "MATCH (u:User)-[:IN]->(pg: ProfileGroup)-[:DEPENDS]->(s:Structure) "
                + "WHERE s.id = {structureId} " + "RETURN collect(distinct u.source) as sources";

        JsonObject params = new JsonObject().put("structureId", structureId);
        neo.execute(query, params, Neo4jResult.validResultHandler(result));
    }

    @Override
    public void listAafFunctions(String structureId, Handler<Either<String, JsonArray>> result) {
        String query = "MATCH (u:User)-[:IN]->(pg: ProfileGroup)-[:DEPENDS]->(s:Structure) "
                + "WHERE s.id = {structureId} "
                + "RETURN collect(DISTINCT EXTRACT(function IN u.functions | last(split(function, \"$\")))) as aafFunctions";

        JsonObject params = new JsonObject().put("structureId", structureId);
        neo.execute(query, params, Neo4jResult.validResultHandler(result));
    }

    @Override
    public void getMetrics(String structureId, Handler<Either<String, JsonObject>> results) {
        String query = "MATCH (s:Structure) " + "WHERE s.id = {structureId} "
                + "MATCH (u:User)-[:IN]->(pg:ProfileGroup)-[:DEPENDS]->(s)," + "(pg)-[:HAS_PROFILE]->(p:Profile) "
                + "WITH p, collect(distinct u) as allUsers "
                + "WITH p, FILTER(u IN allUsers WHERE u.activationCode IS NULL) as active, "
                + "FILTER(u IN allUsers WHERE NOT(u.activationCode IS NULL)) as inactive "
                + "WITH p, length (active) as active, length(inactive) as inactive "
                + "RETURN collect({profile: p.name, active: active, inactive: inactive}) as metrics";

        JsonObject params = new JsonObject().put("structureId", structureId);

        neo.execute(query.toString(), params, validUniqueResultHandler(results));
    }

    @Override
    public void quickSearchUsers(String structureId, String input, Handler<Either<String, JsonArray>> handler) {
        String query = "MATCH (u:User)-[:IN]->(pg:ProfileGroup)-[:DEPENDS]->(s:Structure) " + "WHERE s.id = {id} "
                + "AND u.displayName =~ {inputRegExp} "
                + "RETURN distinct u.id as id, u.firstName as firstName, u.lastName as lastName "
                + "ORDER BY u.lastName";
        String inputRegExp = "(?i).*" + input.trim() + ".*";
        JsonObject params = new JsonObject().put("id", structureId).put("inputRegExp", inputRegExp);
        neo.execute(query, params, Neo4jResult.validResultHandler(handler));
    }

    @Override
    public void userList(String structureId, Handler<Either<String, JsonArray>> handler) {
        String query = "MATCH (u: User)-[:IN]->(pg: ProfileGroup)-[:DEPENDS]->(s: Structure) "
                + "WHERE s.id = {structureId} " + "MATCH (pg)-[:HAS_PROFILE]->(p: Profile) "
                + "OPTIONAL MATCH (u)-[:IN]->(:ProfileGroup)-[:DEPENDS]->(class: Class) "
                + "OPTIONAL MATCH (u)-[d: DUPLICATE]-(duplicate: User)-[:IN]->(:ProfileGroup)-[:DEPENDS]->(sd: Structure) "
                + "OPTIONAL MATCH (u)-[:IN]->(:ProfileGroup)-[:DEPENDS]->(struct: Structure) "
                + "OPTIONAL MATCH (u)-[:IN]->(fgroup: FunctionalGroup) "
                + "OPTIONAL MATCH (u)-[:IN]->(mgroup: ManualGroup) "
                + "OPTIONAL MATCH (u)-[rf:HAS_FUNCTION]->()-[:CONTAINS_FUNCTION*0..1]->(f:Function) "
                + "WITH u, p, class, fgroup, mgroup, f, rf, struct, duplicate, d, collect(DISTINCT {id: sd.id, name: sd.name}) as structuresDup "
                + "RETURN DISTINCT " + "u.id as id, p.name as type, u.activationCode as code, u.login as login,"
                + "u.firstName as firstName, u.lastName as lastName, u.displayName as displayName,"
                + "u.source as source, u.deleteDate as deleteDate, u.disappearanceDate as disappearanceDate, u.blocked as blocked,"
                + "EXTRACT(function IN u.functions | last(split(function, \"$\"))) as aafFunctions,"
                + "CASE WHEN class IS NULL THEN [] ELSE COLLECT(distinct {id: class.id, name: class.name, externalId : class.externalId}) END as classes,"
                + "CASE WHEN fgroup IS NULL THEN [] ELSE COLLECT(distinct fgroup.name) END as functionalGroups, "
                + "CASE WHEN mgroup IS NULL THEN [] ELSE COLLECT(distinct mgroup.name) END as manualGroups, "
                + "CASE WHEN f IS NULL THEN [] ELSE COLLECT(distinct [f.externalId, rf.scope]) END as functions, "
                + "CASE WHEN duplicate IS NULL THEN [] "
                + "ELSE COLLECT(distinct { id: duplicate.id, firstName: duplicate.firstName, lastName: duplicate.lastName, score: d.score, code: duplicate.activationCode, structures: structuresDup }) END as duplicates, "
                + "COLLECT (distinct {id: struct.id, name: struct.name}) as structures "
                + "ORDER BY lastName, firstName " + "UNION " + "MATCH (u: User)-[:HAS_RELATIONSHIPS]->(b: Backup) "
                + "WHERE {structureId} IN b.structureIds AND EXISTS(u.deleteDate)" + "MATCH (s: Structure) "
                + "WHERE s.id IN b.structureIds " + "WITH u, b, s "
                + "RETURN DISTINCT u.id as id, u.profiles[0] as type, u.activationCode as code, u.login as login, u.firstName as firstName, "
                + "u.lastName as lastName, u.displayName as displayName,u.source as source, u.deleteDate as deleteDate, u.disappearanceDate as disappearanceDate, u.blocked as blocked, "
                + "[] as aafFunctions, [] as classes, [] as functionalGroups, [] as manualGroups, [] as functions, [] as duplicates, "
                + "COLLECT(distinct {id: s.id, name: s.name}) as structures " + "ORDER BY lastName, firstName ";

        JsonObject params = new JsonObject().put("structureId", structureId);
        neo.execute(query, params, Neo4jResult.validResultHandler(handler));
    }

    @Override
    public void blockUsers(String structureId, String profile, boolean block, Handler<JsonObject> handler) {
        String query = "MATCH (s:Structure {id:{structureId}})<-[:DEPENDS]-(g:ProfileGroup)<-[:IN]-(u:User) WHERE g.name ENDS WITH {profile} SET u.blocked = {blocked} RETURN COLLECT(DISTINCT u.id) as usersId";
        JsonObject params = new JsonObject().put("structureId", structureId).put("profile", profile).put("blocked",
                block);
        neo.execute(query, params, new Handler<Message<JsonObject>>() {
            @Override
            public void handle(Message<JsonObject> r) {
                ;
                handler.handle(r.body());
            }
        });
    }

    @Override
    public void searchCriteria(List<String> structures, Handler<Either<String, JsonObject>> handler) {
        final String query = "MATCH (s:Structure) " + "WHERE s.id IN {structures} "
                + "OPTIONAL MATCH (s)<-[:BELONGS]-(c:Class) " + "OPTIONAL MATCH (s)<-[:DEPENDS]-(fg:FunctionGroup) "
                + "OPTIONAL MATCH (s)<-[:DEPENDS]-(htg:HTGroup) "
                + "RETURN COLLECT(DISTINCT { id: s.id, name: s.name}) as structures, "
                + "COLLECT(DISTINCT { id: c.id, name: c.name}) as classes, "
                + "CASE WHEN LENGTH(COLLECT(distinct htg)) = 0 THEN COLLECT(DISTINCT fg.filter) ELSE COLLECT(DISTINCT fg.filter) + 'HeadTeacher' END as functions, "
                + "['Teacher', 'Personnel', 'Student', 'Relative', 'Guest'] as profiles, "
                + "['ManualGroup','FunctionalGroup','CommunityGroup'] as groupTypes";
        neo.execute(query, new JsonObject().put("structures", new JsonArray(structures)),
                validUniqueResultHandler(handler));
    }

}