/******************************************************************************* * 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; } }