package org.jboss.windup.reporting.service; import java.util.logging.Logger; import java.util.Map; import java.util.Set; import org.apache.commons.lang3.StringUtils; import org.jboss.windup.config.GraphRewrite; import org.jboss.windup.graph.GraphContext; import org.jboss.windup.graph.model.LinkModel; import org.jboss.windup.graph.model.ProjectModel; import org.jboss.windup.graph.model.WindupVertexFrame; import org.jboss.windup.graph.model.resource.FileModel; import org.jboss.windup.graph.service.FileService; import org.jboss.windup.graph.model.resource.SourceFileModel; import org.jboss.windup.graph.service.GraphService; import org.jboss.windup.graph.traversal.ProjectModelTraversal; import org.jboss.windup.reporting.model.ClassificationModel; import org.jboss.windup.reporting.model.EffortReportModel; import org.jboss.windup.reporting.category.IssueCategoryModel; import org.ocpsoft.rewrite.config.Rule; import org.ocpsoft.rewrite.context.EvaluationContext; import com.thinkaurelius.titan.core.attribute.Text; import com.tinkerpop.blueprints.Compare; import com.tinkerpop.blueprints.Direction; import com.tinkerpop.blueprints.Vertex; import com.tinkerpop.frames.structures.FramedVertexIterable; import com.tinkerpop.gremlin.java.GremlinPipeline; import java.util.logging.Level; import org.jboss.windup.reporting.category.IssueCategoryRegistry; /** * Adds methods for loading and querying ClassificationModel related data. * * @author <a href="mailto:jesse.sightler@gmail.com">Jesse Sightler</a> * */ public class ClassificationService extends GraphService<ClassificationModel> { public static final Logger LOG = Logger.getLogger(ClassificationService.class.getName()); public ClassificationService(GraphContext context) { super(context, ClassificationModel.class); } /** * Returns the total effort points in all of the {@link ClassificationModel}s associated with the provided {@link FileModel}. */ public int getMigrationEffortPoints(FileModel fileModel) { GremlinPipeline<Vertex, Vertex> classificationPipeline = new GremlinPipeline<>(fileModel.asVertex()); classificationPipeline.in(ClassificationModel.FILE_MODEL); classificationPipeline.has(EffortReportModel.EFFORT, Compare.GREATER_THAN, 0); classificationPipeline.has(WindupVertexFrame.TYPE_PROP, Text.CONTAINS, ClassificationModel.TYPE); int classificationEffort = 0; for (Vertex v : classificationPipeline) { Integer migrationEffort = v.getProperty(ClassificationModel.EFFORT); if (migrationEffort != null) { classificationEffort += migrationEffort; } } return classificationEffort; } /** * Return all {@link ClassificationModel} instances that are attached to the given {@link FileModel} instance. */ public Iterable<ClassificationModel> getClassifications(FileModel model) { GremlinPipeline<Vertex, Vertex> pipeline = new GremlinPipeline<>(model.asVertex()); pipeline.in(ClassificationModel.FILE_MODEL); pipeline.has(WindupVertexFrame.TYPE_PROP, Text.CONTAINS, ClassificationModel.TYPE); return new FramedVertexIterable<>(getGraphContext().getFramed(), pipeline, ClassificationModel.class); } /** * Return all {@link ClassificationModel} instances that are attached to the given {@link FileModel} instance with a specific classification name. */ public Iterable<ClassificationModel> getClassificationByName(FileModel model, String classificationName) { GremlinPipeline<Vertex, Vertex> pipeline = new GremlinPipeline<>(model.asVertex()); pipeline.in(ClassificationModel.FILE_MODEL); pipeline.has(WindupVertexFrame.TYPE_PROP, Text.CONTAINS, ClassificationModel.TYPE); pipeline.has(ClassificationModel.CLASSIFICATION, classificationName); return new FramedVertexIterable<>(getGraphContext().getFramed(), pipeline, ClassificationModel.class); } /** * <p> * Returns the total effort points in all of the {@link ClassificationModel}s * associated with the {@link FileModel} instances in the given {@link ProjectModelTraversal}. * </p> * <p> * If set to recursive, then also include the effort points from child projects. * </p> * <p> * The result is a Map, the key contains the effort level and the value contains the number of incidents. * </p> */ public Map<Integer, Integer> getMigrationEffortByPoints(ProjectModelTraversal traversal, Set<String> includeTags, Set<String> excludeTags, boolean recursive, boolean includeZero) { MapSumEffortAccumulatorFunction<Integer> accumulator = new MapSumEffortAccumulatorFunction() { public Integer vertexToKey(Vertex effortReportVertex) { Integer migrationEffort = effortReportVertex.getProperty(EffortReportModel.EFFORT); return migrationEffort; } }; getMigrationEffortDetails(traversal, includeTags, excludeTags, recursive, includeZero, accumulator); return accumulator.getResults(); } /** * Returns the total incidents in all of the {@link ClassificationModel}s associated with the files in this project by severity. */ public Map<IssueCategoryModel, Integer> getMigrationEffortBySeverity(GraphRewrite event, ProjectModelTraversal traversal, Set<String> includeTags, Set<String> excludeTags, boolean recursive) { MapSumEffortAccumulatorFunction<IssueCategoryModel> accumulator = new MapSumEffortAccumulatorFunction<IssueCategoryModel>() { public IssueCategoryModel vertexToKey(Vertex effortReportVertex) { return frame(effortReportVertex).getIssueCategory(); } }; this.getMigrationEffortDetails(traversal, includeTags, excludeTags, recursive, true, accumulator); return accumulator.getResults(); } private void getMigrationEffortDetails(ProjectModelTraversal traversal, Set<String> includeTags, Set<String> excludeTags, boolean recursive, boolean includeZero, EffortAccumulatorFunction accumulatorFunction) { LOG.log(Level.INFO, String.format("\n\t\t\tEFFORT C: getMigrationEffortDetails() with: %s, %srecur, %sincludeZero, %s, tags: %s, excl: %s", traversal, recursive ? "" : "!", includeZero ? "" : "!", accumulatorFunction, includeTags, excludeTags)); final Set<Vertex> initialVertices = traversal.getAllProjectsAsVertices(recursive); GremlinPipeline<Vertex, Vertex> pipeline = new GremlinPipeline<>(this.getGraphContext().getGraph()); pipeline.V(); // If the multivalue index is not 1st, then it doesn't work - https://github.com/thinkaurelius/titan/issues/403 if (!includeZero) { pipeline.has(EffortReportModel.EFFORT, Compare.GREATER_THAN, 0); pipeline.has(WindupVertexFrame.TYPE_PROP, Text.CONTAINS, ClassificationModel.TYPE); } else { pipeline.has(WindupVertexFrame.TYPE_PROP, ClassificationModel.TYPE); } pipeline.as("classification"); // For each classification, count it repeatedly for each file that is within given set of Projects (from the traversal). pipeline.out(ClassificationModel.FILE_MODEL); pipeline.in(ProjectModel.PROJECT_MODEL_TO_FILE); pipeline.filter(new SetMembersFilter(initialVertices)); pipeline.back("classification"); boolean checkTags = !includeTags.isEmpty() || !excludeTags.isEmpty(); FileService fileService = new FileService(getGraphContext()); for (Vertex v : pipeline) { // only check tags if we have some passed in if (checkTags && !frame(v).matchesTags(includeTags, excludeTags)) continue; // For each classification, count it repeatedly for each file. // TODO: .accumulate(v, count); // TODO: This could be all done just within the query (provided that the tags would be taken care of). // Accumulate could be a PipeFunction. for (Vertex fileVertex : v.getVertices(Direction.OUT, ClassificationModel.FILE_MODEL)) { // Make sure that this file is actually in an accepted project. The pipeline condition will return // classifications that aren't necessarily in the same project. FileModel fileModel = fileService.frame(fileVertex); if (initialVertices.contains(fileModel.getProjectModel().asVertex())) accumulatorFunction.accumulate(v); } } } /** * Attach a {@link ClassificationModel} with the given classificationText and description to the provided {@link FileModel}. * If an existing Model exists with the provided classificationText, that one will be used instead. */ public ClassificationModel attachClassification(GraphRewrite event, Rule rule, FileModel fileModel, String classificationText, String description) { return attachClassification(event, rule, fileModel, IssueCategoryRegistry.DEFAULT, classificationText, description); } /** * Attach a {@link ClassificationModel} with the given classificationText and description to the provided {@link FileModel}. * If an existing Model exists with the provided classificationText, that one will be used instead. */ public ClassificationModel attachClassification(GraphRewrite event, Rule rule, FileModel fileModel, String categoryId, String classificationText, String description) { ClassificationModel classification = getUnique(getTypedQuery().has(ClassificationModel.CLASSIFICATION, classificationText)); if (classification == null) { classification = create(); classification.setClassification(classificationText); classification.setDescription(description); classification.setEffort(0); IssueCategoryModel cat = IssueCategoryRegistry.loadFromGraph(event.getGraphContext(), categoryId); classification.setIssueCategory(cat); classification.setRuleID(rule.getId()); classification.addFileModel(fileModel); if (fileModel instanceof SourceFileModel) ((SourceFileModel) fileModel).setGenerateSourceReport(true); ClassificationServiceCache.cacheClassificationFileModel(event, classification, fileModel, true); return classification; } return attachClassification(event, classification, fileModel); } /** * Attach a {@link ClassificationModel} with the given classificationText and description to the provided {@link FileModel}. * If an existing Model exists with the provided classificationText, that one will be used instead. */ public ClassificationModel attachClassification(GraphRewrite event, EvaluationContext context, FileModel fileModel, String classificationText, String description) { return attachClassification(event, context, fileModel, IssueCategoryRegistry.DEFAULT, classificationText, description); } public ClassificationModel attachClassification(GraphRewrite event, EvaluationContext context, FileModel fileModel, String categoryId, String classificationText, String description) { Rule rule = (Rule) context.get(Rule.class); return attachClassification(event, rule, fileModel, categoryId, classificationText, description); } private boolean isClassificationLinkedToFileModel(GraphRewrite event, ClassificationModel classificationModel, FileModel fileModel) { return ClassificationServiceCache.isClassificationLinkedToFileModel(event, classificationModel, fileModel); } /** * This method just attaches the {@link ClassificationModel} to the {@link FileModel}. * It will only do so if this link is not already present. */ public ClassificationModel attachClassification(GraphRewrite event, ClassificationModel classificationModel, FileModel fileModel) { if (!isClassificationLinkedToFileModel(event, classificationModel, fileModel)) { classificationModel.addFileModel(fileModel); if (fileModel instanceof SourceFileModel) ((SourceFileModel) fileModel).setGenerateSourceReport(true); } ClassificationServiceCache.cacheClassificationFileModel(event, classificationModel, fileModel, true); return classificationModel; } /** * Attach the given link to the classification, while checking for duplicates. */ public ClassificationModel attachLink(ClassificationModel classificationModel, LinkModel linkModel) { for (LinkModel existing : classificationModel.getLinks()) { if (StringUtils.equals(existing.getLink(), linkModel.getLink())) { return classificationModel; } } classificationModel.addLink(linkModel); return classificationModel; } }