net.atos.entng.statistics.services.StatisticsServiceMongoImpl.java Source code

Java tutorial

Introduction

Here is the source code for net.atos.entng.statistics.services.StatisticsServiceMongoImpl.java

Source

/*
 * Copyright  Rgion Nord Pas de Calais-Picardie, 2016.
 *
 * This file is part of OPEN ENT NG. OPEN ENT NG is a versatile ENT Project based on the JVM and ENT Core Project.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation (version 3 of the License).
 *
 * For the sake of explanation, any module that communicate over native
 * Web protocols, such as HTTP, with OPEN ENT NG is outside the scope of this
 * license and could be license under its own terms. This is merely considered
 * normal use of OPEN ENT NG, and does not fall under the heading of "covered work".
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

package net.atos.entng.statistics.services;

import static net.atos.entng.statistics.DateUtils.formatTimestamp;
import static net.atos.entng.statistics.controllers.StatisticsController.*;
import static net.atos.entng.statistics.aggregation.indicators.IndicatorConstants.STATS_FIELD_ACTIVATED_ACCOUNTS;
import static net.atos.entng.statistics.aggregation.indicators.IndicatorConstants.STATS_FIELD_ACCOUNTS;
import static org.entcore.common.aggregation.MongoConstants.TRACE_TYPE_SVC_ACCESS;
import static org.entcore.common.aggregation.MongoConstants.TRACE_FIELD_MODULE;
import static org.entcore.common.aggregation.MongoConstants.TRACE_FIELD_PROFILE;
import static org.entcore.common.aggregation.MongoConstants.TRACE_FIELD_STRUCTURES;
import static org.entcore.common.aggregation.MongoConstants.STATS_FIELD_DATE;
import static org.entcore.common.aggregation.MongoConstants.STATS_FIELD_GROUPBY;

import java.util.List;

import org.entcore.common.mongodb.MongoDbResult;
import org.entcore.common.service.impl.MongoDbCrudService;
import org.vertx.java.core.Handler;
import org.vertx.java.core.eventbus.Message;
import org.vertx.java.core.json.JsonArray;
import org.vertx.java.core.json.JsonObject;

import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.QueryBuilder;

import fr.wseduc.mongodb.MongoDb;
import fr.wseduc.mongodb.MongoQueryBuilder;
import fr.wseduc.webutils.Either;

public class StatisticsServiceMongoImpl extends MongoDbCrudService implements StatisticsService {

    private final String collection;
    private final MongoDb mongo;

    public static final String MODULE_ID = TRACE_FIELD_MODULE + "_id";
    public static final String PROFILE_ID = TRACE_FIELD_PROFILE + "_id";
    public static final String STRUCTURES_ID = TRACE_FIELD_STRUCTURES + "_id";

    private static final JsonObject sortByDateProfile = new JsonObject().putNumber(STATS_FIELD_DATE, 1)
            .putNumber(PROFILE_ID, 1);
    private static final JsonObject sortByStructureDateProfile = new JsonObject().putNumber(STRUCTURES_ID, 1)
            .putNumber(STATS_FIELD_DATE, 1).putNumber(PROFILE_ID, 1);

    public StatisticsServiceMongoImpl(String collection) {
        super(collection);
        this.collection = collection;
        this.mongo = MongoDb.getInstance();
    }

    @Override
    public void getStats(final List<String> schoolIds, final JsonObject params,
            final Handler<Either<String, JsonArray>> handler) {
        this.getStatistics(schoolIds, params, handler, false);
    }

    @Override
    public void getStatsForExport(final List<String> schoolIds, final JsonObject params,
            final Handler<Either<String, JsonArray>> handler) {
        this.getStatistics(schoolIds, params, handler, true);
    }

    private void getStatistics(final List<String> schoolIds, final JsonObject params,
            final Handler<Either<String, JsonArray>> handler, boolean isExport) {
        if (schoolIds == null || schoolIds.isEmpty()) {
            throw new IllegalArgumentException("schoolIds is null or empty");
        }

        String indicator = params.getString(PARAM_INDICATOR);
        Long start = (Long) params.getNumber(PARAM_START_DATE);
        Long end = (Long) params.getNumber(PARAM_END_DATE);

        boolean isActivatedAccountsIndicator = STATS_FIELD_ACTIVATED_ACCOUNTS.equals(indicator);
        boolean isAccessIndicator = TRACE_TYPE_SVC_ACCESS.equals(indicator);
        String groupedBy = isAccessIndicator ? "module/structures/profil" : "structures/profil";
        final QueryBuilder criteriaQuery = QueryBuilder.start(STATS_FIELD_GROUPBY).is(groupedBy)
                .and(STATS_FIELD_DATE).greaterThanEquals(formatTimestamp(start)).lessThan(formatTimestamp(end))
                .and(indicator).exists(true);

        String module = params.getString(PARAM_MODULE);
        boolean moduleIsEmpty = module == null || module.trim().isEmpty();
        boolean isAccessAllModules = isAccessIndicator && moduleIsEmpty;
        if (isAccessIndicator && !moduleIsEmpty) {
            criteriaQuery.and(MODULE_ID).is(module);
        }

        if (schoolIds.size() > 1) {
            criteriaQuery.and(STRUCTURES_ID).in(schoolIds);
        } else {
            criteriaQuery.and(STRUCTURES_ID).is(schoolIds.get(0));

            // When getting data for only one module, a "find" is enough (no need to aggregate data)
            if (!isExport && !isAccessAllModules) {
                JsonObject projection = new JsonObject();
                projection.putNumber("_id", 0).putNumber(indicator, 1).putNumber(PROFILE_ID, 1)
                        .putNumber(STATS_FIELD_DATE, 1);
                if (isActivatedAccountsIndicator) {
                    projection.putNumber(STATS_FIELD_ACCOUNTS, 1);
                }

                mongo.find(collection, MongoQueryBuilder.build(criteriaQuery), sortByDateProfile, projection,
                        MongoDbResult.validResultsHandler(handler));
                return;
            }
        }

        // Aggregate data
        final JsonObject aggregation = new JsonObject();
        JsonArray pipeline = new JsonArray();
        aggregation.putString("aggregate", collection).putBoolean("allowDiskUse", true).putArray("pipeline",
                pipeline);

        pipeline.addObject(new JsonObject().putObject("$match", MongoQueryBuilder.build(criteriaQuery)));

        JsonObject id = new JsonObject().putString(PROFILE_ID, "$" + PROFILE_ID);
        if (isAccessAllModules && !isExport) {
            // Case : get JSON data for indicator "access to all modules"
            id.putString(MODULE_ID, "$" + MODULE_ID);
        } else {
            id.putString(STATS_FIELD_DATE, "$" + STATS_FIELD_DATE);
        }

        JsonObject group = new JsonObject().putObject("_id", id).putObject(indicator,
                new JsonObject().putString("$sum", "$" + indicator));
        if (isActivatedAccountsIndicator) {
            group.putObject(STATS_FIELD_ACCOUNTS, new JsonObject().putString("$sum", "$" + STATS_FIELD_ACCOUNTS));
        }
        JsonObject groupBy = new JsonObject().putObject("$group", group);

        pipeline.addObject(groupBy);

        QueryBuilder projection = QueryBuilder.start("_id").is(0).and(PROFILE_ID).is("$_id." + PROFILE_ID);
        if (isActivatedAccountsIndicator) {
            projection.and(STATS_FIELD_ACCOUNTS).is(1);
        }

        if (!isExport) {
            projection.and(indicator).is(1);
            if (isAccessAllModules) {
                projection.and(MODULE_ID).is("$_id." + MODULE_ID);
            } else {
                projection.and(STATS_FIELD_DATE).is("$_id." + STATS_FIELD_DATE);
            }

            // Sum stats for all structure_ids
            pipeline.addObject(new JsonObject().putObject("$project", MongoQueryBuilder.build(projection)));
        } else {
            // Projection : keep 'yyyy-MM' from 'yyyy-MM-dd HH:mm.ss.SSS'
            DBObject dateSubstring = new BasicDBObject();
            BasicDBList dbl = new BasicDBList();
            dbl.add("$_id." + STATS_FIELD_DATE);
            dbl.add(0);
            dbl.add(7);
            dateSubstring.put("$substr", dbl);

            projection.and(STATS_FIELD_DATE).is(dateSubstring).and("indicatorValue").is("$" + indicator); // Replace indicatorName by label "indicatorValue", so that the mustache template can be generic

            JsonObject sort = sortByStructureDateProfile;

            // Export stats for each structure_id
            id.putString(STRUCTURES_ID, "$" + STRUCTURES_ID);
            projection.and(STRUCTURES_ID).is("$_id." + STRUCTURES_ID);

            if (isAccessIndicator) {
                if (isAccessAllModules) {
                    sort = sort.copy().putNumber(MODULE_ID, 1);
                }

                // Export stats for each module_id
                id.putString(MODULE_ID, "$" + MODULE_ID);
                projection.and(MODULE_ID).is("$_id." + MODULE_ID);
            }

            pipeline.addObject(new JsonObject().putObject("$project", MongoQueryBuilder.build(projection)));
            pipeline.addObject(new JsonObject().putObject("$sort", sort));
        }

        mongo.command(aggregation.toString(), new Handler<Message<JsonObject>>() {
            @Override
            public void handle(Message<JsonObject> message) {
                if ("ok".equals(message.body().getString("status"))
                        && message.body().getObject("result", new JsonObject()).getInteger("ok") == 1) {
                    JsonArray result = message.body().getObject("result").getArray("result");
                    handler.handle(new Either.Right<String, JsonArray>(result));
                } else {
                    String error = message.body().toString();
                    handler.handle(new Either.Left<String, JsonArray>(error));
                }
            }
        });
    }

}