eu.cloudwave.wp5.feedbackhandler.repositories.ProcedureExecutionRepositoryImpl.java Source code

Java tutorial

Introduction

Here is the source code for eu.cloudwave.wp5.feedbackhandler.repositories.ProcedureExecutionRepositoryImpl.java

Source

/*******************************************************************************
 * Copyright 2015 Software Evolution and Architecture Lab, University of Zurich
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 ******************************************************************************/
package eu.cloudwave.wp5.feedbackhandler.repositories;

import static org.springframework.data.mongodb.core.aggregation.Aggregation.group;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.match;
import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationResults;
import org.springframework.data.mongodb.core.aggregation.Fields;
import org.springframework.data.mongodb.core.aggregation.GroupOperation;
import org.springframework.data.mongodb.core.aggregation.MatchOperation;
import org.springframework.data.mongodb.core.query.BasicQuery;
import org.springframework.data.mongodb.core.query.Criteria;

import com.google.common.base.Optional;

import eu.cloudwave.wp5.common.constants.Ids;
import eu.cloudwave.wp5.feedbackhandler.aggregations.AggregatedInvocation;
import eu.cloudwave.wp5.feedbackhandler.aggregations.ClientRequestCollector;
import eu.cloudwave.wp5.feedbackhandler.aggregations.IncomingRequestCollector;
import eu.cloudwave.wp5.feedbackhandler.constants.DbTableNames;

public class ProcedureExecutionRepositoryImpl extends AbstractRepository
        implements ProcedureExecutionRepositoryCustom {

    @Autowired
    private MongoTemplate mongoTemplate;

    /**
     * {@inheritDoc}
     */
    @Override
    public List<ClientRequestCollector> getAllRequests(final Long timeRangeFrom, final Long timeRangeTo) {
        Criteria matchCriteria = getClientRequestAnnotationCriteria();
        matchCriteria = addTimeRangeCriteria(matchCriteria, timeRangeFrom, timeRangeTo);

        return getRequestsWithCriteria(matchCriteria);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<ClientRequestCollector> getRequestsByCallee(final String callee, final Long timeRangeFrom,
            final Long timeRangeTo) {
        Criteria matchCriteria = getClientRequestAnnotationCriteria();
        matchCriteria = matchCriteria.and(ANNOTATION_TO_ATTRIBUTE).is(callee);
        matchCriteria = addTimeRangeCriteria(matchCriteria, timeRangeFrom, timeRangeTo);

        return getRequestsWithCriteria(matchCriteria);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<ClientRequestCollector> getRequestsByCaller(String caller, final Long timeRangeFrom,
            final Long timeRangeTo) {
        Criteria matchCriteria = getClientRequestAnnotationCriteria();
        matchCriteria = matchCriteria.and(ANNOTATION_FROM_ATTRIBUTE).is(caller);
        matchCriteria = addTimeRangeCriteria(matchCriteria, timeRangeFrom, timeRangeTo);

        return getRequestsWithCriteria(matchCriteria);
    }

    /**
     * {@inheritDoc}
     */
    public List<IncomingRequestCollector> getAllIncomingRequests(final Long timeRangeFrom, final Long timeRangeTo) {
        Criteria matchCriteria = getMicroserviceMethodDeclarationAnnotationCriteria();
        matchCriteria = addTimeRangeCriteria(matchCriteria, timeRangeFrom, timeRangeTo);

        return getIncomingRequestsWithCriteria(matchCriteria, true);
    }

    /**
     * {@inheritDoc}
     */
    public List<IncomingRequestCollector> getIncomingByIdentifier(final String identifier, final Long timeRangeFrom,
            final Long timeRangeTo, boolean groupByMethod) {
        //@formatter:off
        Criteria matchCriteria = getMicroserviceMethodDeclarationAnnotationCriteria()
                .and(ANNOTATION_IDENTIFIER_ATTRIBUTE).is(identifier);
        //@formatter:on

        matchCriteria = addTimeRangeCriteria(matchCriteria, timeRangeFrom, timeRangeTo);

        return getIncomingRequestsWithCriteria(matchCriteria, groupByMethod);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Optional<AggregatedInvocation> getCallersOfInvokedMethod(String invokedClassName,
            String invokedMethodName, String callerClassName, String callerMethodName) {
        // objects have to have the same method name + class name
        final MatchOperation matchOperation = match(
                new Criteria(PROC__NAME).is(invokedMethodName).and(PROC__CLASS_NAME).is(invokedClassName));

        /*
         * add all distinct callers of the given invoked method to set
         * 
         * Fields.from(Fields.field(<NEW ATTRIBUTE NAME>, <ORIGINAL ATTRIBUTE NAME>))
         */
        final GroupOperation groupOperation = group(
                Fields.from(Fields.field(INVOKED_METHOD_NAME_PROJECTION, PROC__NAME))
                        .and(INVOKED_CLASS_NAME_PROJECTION, PROC__CLASS_NAME)).addToSet("$" + CALLER)
                                .as(CALLER_AGGREGATION_ATTRIBUTE);

        List<AggregatedInvocation> aggregationResults = aggregateProcedureExecution(
                newAggregation(matchOperation, groupOperation), AggregatedInvocation.class).getMappedResults();
        if (!aggregationResults.isEmpty()) {
            return Optional.of(aggregationResults.get(0));
        }
        return Optional.absent();
    }

    /**
     * Criteria that filters requests with @MicroserviceClientMethodDeclaration annotation
     * 
     * @return {@link Criteria}
     */
    private Criteria getClientRequestAnnotationCriteria() {
        return new Criteria(ANNOTATION_NAME).is(Ids.MICROSERVICE_CLIENT_REQUEST_ANNOTATION_IDENTIFIER);
    }

    /**
     * Criteria that filters requests with @MicroserviceMethodDeclaration annotation.
     * 
     * @return {@link Criteria}
     */
    private Criteria getMicroserviceMethodDeclarationAnnotationCriteria() {
        return new Criteria(ANNOTATION_NAME).is(Ids.MICROSERVICE_ENDPOINT_ANNOTATION_IDENTIFIER);
    }

    /**
     * Helper that builds and runs the query. It creates a Match Operation, Group Operation and the Aggregation. The
     * GroupOperation groups by caller, callee, calleeMethod and method name.
     * 
     * @param matchCriteria
     *          {@link Criteria} which specifies the annotation and other filters
     * @return {@link ClientRequestCollector} a list of matching requests
     */
    private List<ClientRequestCollector> getRequestsWithCriteria(Criteria matchCriteria) {

        // create match operation based on input
        final MatchOperation matchOperation = match(matchCriteria);

        // 1. group by caller
        Fields fields = Fields.from(Fields.field(Ids.MICROSERVICE_CLIENT_REQUEST_ANNOTATION_FROM_ATTRIBUTE,
                "$" + ANNOTATION_FROM_ATTRIBUTE));

        // 2. group by callee
        fields = fields.and(Ids.MICROSERVICE_CLIENT_REQUEST_ANNOTATION_TO_ATTRIBUTE, "$" + ANNOTATION_TO_ATTRIBUTE);

        // 3. group by callee method
        fields = fields.and(Ids.MICROSERVICE_CLIENT_REQUEST_ANNOTATION_TO_METHOD_ATTRIBUTE,
                "$" + ANNOTATION_TO_METHOD_ATTRIBUTE);

        // 4. group by name of method that contains the invocation
        fields = fields.and(METHOD_PROJECTION, "$" + METHOD_ATTRIBUTE);

        // create group operation
        final GroupOperation groupOperation = group(fields).push("$" + TIME_FIELD).as(TIME_AGGREGATION_ATTRIBUTE);
        final Aggregation microserviceClientAggregationSpec = newAggregation(matchOperation, groupOperation);

        return aggregateProcedureExecution(microserviceClientAggregationSpec, ClientRequestCollector.class)
                .getMappedResults();
    }

    /**
     * Helper that builds and runs the query. It creates a Match Operation, Group Operation and the Aggregation. The
     * GroupOperation groups by caller, callee, calleeMethod and method name.
     * 
     * @param matchCriteria
     *          {@link Criteria} which specifies the annotation and other filters
     * @return {@link IncomingRequestCollector} a list of matching requests
     */
    private List<IncomingRequestCollector> getIncomingRequestsWithCriteria(Criteria matchCriteria,
            boolean groupByMethod) {
        // create match operation based on input
        final MatchOperation matchOperation = match(matchCriteria);

        // 1. group by service identifier
        Fields fields = Fields.from(Fields.field(Ids.MICROSERVICE_ENDPOINT_ANNOTATION_IDENTIFIER_ATTRIBUTE,
                "$" + ANNOTATION_IDENTIFIER_ATTRIBUTE));

        // 2. group by service method
        if (groupByMethod) {
            fields = fields.and(Ids.MICROSERVICE_DECLARATION_ANNOTATION_METHOD_ATTRIBUTE,
                    "$" + ANNOTATION_METHOD_ATTRIBUTE);
        }

        // create group operation and push all timestamps into a list
        final GroupOperation groupOperation = group(fields).push("$" + TIME_FIELD).as(TIME_AGGREGATION_ATTRIBUTE);
        final Aggregation microserviceClientAggregationSpec = newAggregation(matchOperation, groupOperation);

        return aggregateProcedureExecution(microserviceClientAggregationSpec, IncomingRequestCollector.class)
                .getMappedResults();
    }

    /**
     * Helper method that adds time criteria to given match criteria
     * 
     * @param criteria
     * @param timeRangeFrom
     * @param timeRangeTo
     * @return criteria
     */
    private Criteria addTimeRangeCriteria(Criteria criteria, Long timeRangeFrom, Long timeRangeTo) {
        if (timeRangeFrom == null && timeRangeTo == null) {
            return criteria;
        } else if (timeRangeFrom != null && timeRangeTo == null) {
            // greater than or equal
            return criteria.and(TIME_FIELD).gte(timeRangeFrom);
        } else if (timeRangeFrom == null && timeRangeTo != null) {
            // less than or equal
            return criteria.and(TIME_FIELD).lte(timeRangeTo);
        } else {
            // We have to use the andOperator instead of and() due to limitations of the com.mongodb.BasicDBObject when it
            // comes to multiple criteria on the same field (TIME_VALUE)
            return criteria.andOperator(Criteria.where(TIME_FIELD).gte(timeRangeFrom),
                    Criteria.where(TIME_FIELD).lte(timeRangeTo));
        }
    }

    /**
     * Helper that aggregates ProcedureExecutions
     * 
     * @param aggregation
     * @param outputType
     * @return
     */
    private <O> AggregationResults<O> aggregateProcedureExecution(final Aggregation aggregation,
            final Class<O> outputType) {
        return mongoTemplate.aggregate(aggregation, DbTableNames.PROCEDURE_EXECUTIONS, outputType);
    }

    /**
     * {@inheritDoc} TODO: remove when not needed anymore
     */
    @Override
    public List<ClientRequestCollector> basicQuery() {
        List<ClientRequestCollector> result = mongoTemplate.find(new BasicQuery(
                "[{ '$match' : { 'procedure.annotations.name': { $regex: '.*MicroserviceClientRequest' }, 'procedure.annotations.members.caller' : 'eu.cloudwave.samples.services.currency'}},{ $project : {     'startTimeGrouping' : { '$subtract' : [ { $divide : ['$startTime', 3600 ]}, { $mod : [{ $divide : ['$startTime', 3600 ]},1] } ] },'caller': '$procedure.annotations.members.caller','callee': '$procedure.annotations.members.callee'} }]"),
                ClientRequestCollector.class);
        return result;
    }
}