/*******************************************************************************
* 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.feedback.eclipse.costs.core.builders.participants;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import org.eclipse.core.resources.IMarker;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import eu.cloudwave.wp5.common.constants.Ids;
import eu.cloudwave.wp5.common.dto.ApplicationDto;
import eu.cloudwave.wp5.common.dto.costs.AggregatedIncomingRequestsDto;
import eu.cloudwave.wp5.common.dto.costs.AggregatedMicroserviceRequestsDto;
import eu.cloudwave.wp5.common.dto.costs.AggregatedRequestsDto;
import eu.cloudwave.wp5.common.dto.costs.InitialInvocationCheckDto;
import eu.cloudwave.wp5.common.model.Prediction;
import eu.cloudwave.wp5.feedback.eclipse.base.core.builders.participants.FeedbackBuilderParticipant;
import eu.cloudwave.wp5.feedback.eclipse.base.resources.core.java.FeedbackJavaFile;
import eu.cloudwave.wp5.feedback.eclipse.base.resources.core.java.FeedbackJavaProject;
import eu.cloudwave.wp5.feedback.eclipse.base.resources.markers.MarkerAttributes;
import eu.cloudwave.wp5.feedback.eclipse.base.resources.markers.MarkerPosition;
import eu.cloudwave.wp5.feedback.eclipse.base.resources.markers.MarkerSpecification;
import eu.cloudwave.wp5.feedback.eclipse.costs.core.CostIds;
import eu.cloudwave.wp5.feedback.eclipse.costs.core.CostPluginActivator;
import eu.cloudwave.wp5.feedback.eclipse.costs.core.markers.CostMarkerTypes;
import eu.cloudwave.wp5.feedback.eclipse.costs.core.predictions.PredictionStrategy;
import eu.cloudwave.wp5.feedback.eclipse.costs.ui.hovers.CostContextBuilder;
/**
* A builder participant that is responsible for the client invocation hovers. A hover will be added whenever you call a
* method of a class that is marked as {@link Ids#MICROSERVICE_CLIENT_REQUEST_ANNOTATION}. <br />
* <br />
* The {@link FeedbackBuilderParticipant} calls the
* {@link MicroserviceClientInvocationParticipant#buildFile(FeedbackJavaProject, FeedbackJavaFile, CompilationUnit)}
* method for every file in your workspace that is built.
*/
public class MicroserviceClientInvocationParticipant extends AbstractCostFeedbackBuilderParticipant implements FeedbackBuilderParticipant {
/**
* The name of the template which should be used to display the hover
*/
private final static String HOVER_TEMPLATE = "clientInvocation";
/**
* The name of the annotation which this MicroserviceClientInvocationParticipant cares about
*/
private final static String targetAnnotation = "@" + Ids.MICROSERVICE_CLIENT_REQUEST_ANNOTATION;
/**
* The prediction strategy that predicts cost impacts of changes
*/
private PredictionStrategy strategy;
/**
* Constructor that initializes implementation of cost prediction strategy
*/
public MicroserviceClientInvocationParticipant() {
strategy = CostPluginActivator.instance(PredictionStrategy.class);
}
/**
* Building files in which microservice client methods are invoked
*/
@Override
protected void buildFile(FeedbackJavaProject project, FeedbackJavaFile javaFile, CompilationUnit astRoot) {
// System.out.println("ClientRequestParticipant buildFile");
astRoot.accept(new ASTVisitor() {
private AggregatedMicroserviceRequestsDto overallRequest;
String currentMethodDeclaration;
String currentMethodDeclarationClassname;
@Override
public boolean visit(MethodInvocation node) {
String invokedMethodName = node.getName().getIdentifier();
IAnnotationBinding[] annotationsOfNode = node.resolveMethodBinding().getMethodDeclaration().getAnnotations();
// Does the current node really have a MicroserviceClientInvocation Annotation?
// we do not go through the whole list, the filter is only going to be applied until we reach any valid element
Optional<IAnnotationBinding> annotationCheck = Arrays.asList(annotationsOfNode).stream().filter(m -> m.toString().startsWith(targetAnnotation)).findAny();
if (annotationCheck.isPresent()) {
System.out.println("Client " + invokedMethodName + " is called within the method " + this.currentMethodDeclaration);
// YahooClient
String invokedClassname = node.getExpression().resolveTypeBinding().getQualifiedName();
String invokedServiceIdentifier = extractAttributeValueFromAnnotation(annotationCheck.get().getAllMemberValuePairs(), Ids.MICROSERVICE_CLIENT_REQUEST_ANNOTATION_TO_ATTRIBUTE);
/*
* This participant looks for client invocations. This means we call a class which is a client of another
* Microservice x. In the following lines we'll try to detect the status of this service x (number of
* instances, price per instance, etc.)
*/
ApplicationDto app = getApplication(invokedServiceIdentifier);
AggregatedRequestsDto requestsToInvokedService = getOverallRequestsOfApplication(invokedServiceIdentifier);
List<Prediction> predictions = null;
boolean isNewlyInvoked = checkIfMethodInvocationIsNew(new InitialInvocationCheckDto(invokedClassname, invokedMethodName, currentMethodDeclarationClassname, currentMethodDeclaration));
if (isNewlyInvoked) {
predictions = strategy.predict(app, requestsToInvokedService, getOverallRequestsByCallee());
}
/*
* Depending on the user's properties we create and add a marker...
*/
if ((showNewInvocationHover && isNewlyInvoked) || (showExistingInvocationHover && !isNewlyInvoked)) {
// Marker Specification
final int startPosition = node.getStartPosition() + node.getExpression().getLength() + 1; // +1 for the dot
final int line = astRoot.getLineNumber(startPosition);
final int endPosition = startPosition + node.getName().getLength();
final MarkerPosition position = new MarkerPosition(line, startPosition, endPosition);
final String markerInfoTitle = "Microservice Client Invocation " + node.getName().toString();
MarkerSpecification costMarker = MarkerSpecification.of(CostIds.COST_MARKER, position, IMarker.SEVERITY_INFO, CostMarkerTypes.CLIENT_INVOCATION, markerInfoTitle);
/*
* Preparation of the hover content which is rendered by Freemarker. The template file is specified above
* and can be found in the OSGI-INF/l10n/templates folder.
*/
// @formatter:off
costMarker = costMarker.and(MarkerAttributes.DESCRIPTION, templateHandler.getContent(HOVER_TEMPLATE, CostContextBuilder.init()
.setTimeParameters(timeRangeFrom, timeRangeTo, aggregationInterval)
.setRequestStats("overall", getOverallRequestsByCallee())
.setApplication(app)
// substring from the properties: (eu.cloudwave.samples.services.) currency
.add("serviceIdentifier", serviceIdentifier)
.add("invokedClassname", invokedClassname)
.add("invokedMethodName", invokedMethodName)
.add("isNew", isNewlyInvoked)
.addIfNotNull("predictions", predictions)
.build()));
// @formatter:on
addMarker(javaFile, costMarker);
}
}
return false; // do not go further to children
}
/**
* It looks like this is the easiest way to know the name of the method which is responsible for the current
* MethodInvocation
*
* @param node
*
* @return true which means "go further to children"
*/
@Override
public boolean visit(MethodDeclaration node) {
currentMethodDeclaration = node.getName().getIdentifier();
currentMethodDeclarationClassname = node.resolveBinding().getDeclaringClass().getQualifiedName();
return true;
}
/**
* Overall request statistics of the current project/service
*
* @return AggregatedMicroserviceRequestsDto with min, avg and max
*/
private AggregatedMicroserviceRequestsDto getOverallRequestsByCallee() {
if (overallRequest == null) {
overallRequest = feedbackHandlerClient.requestsByCalleeOverall(project, aggregationInterval, timeRangeFrom, timeRangeTo);
}
return overallRequest;
}
/**
* Returns an application
*
* @return {@link ApplicationDto}
*/
private ApplicationDto getApplication(final String applicationId) {
ApplicationDto app = cache.get(applicationId);
if (app == null) {
try {
app = cache.addAndReturn(applicationId, feedbackHandlerClient.application(project, applicationId));
}
catch (Exception e) {}
}
return app;
}
private AggregatedIncomingRequestsDto getOverallRequestsOfApplication(final String applicationId) {
return feedbackHandlerClient.overallIncomingRequestsByIdentifier(project, applicationId, aggregationInterval, timeRangeFrom, timeRangeTo);
}
/**
* Check if client method invocation was newly added to the current method
*
* @param invocationDto
* @return boolean that indicates if this invocation was newly added to the current method
*/
private boolean checkIfMethodInvocationIsNew(final InitialInvocationCheckDto invocationDto) {
if (invocationDto.getCallerClassName() == null || invocationDto.getCallerMethodName() == null) {
return true;
}
return feedbackHandlerClient.isNewlyInvoked(project, invocationDto);
}
});
}
}