package org.jboss.windup.reporting.category; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import com.thinkaurelius.titan.core.TitanGraph; import com.tinkerpop.blueprints.util.wrappers.event.EventGraph; import com.tinkerpop.frames.FramedGraph; import org.jboss.forge.furnace.util.OperatingSystemUtils; import org.jboss.windup.config.GraphRewrite; import org.jboss.windup.config.loader.RuleLoaderContext; import org.jboss.windup.graph.GraphContext; import org.jboss.windup.graph.model.WindupVertexFrame; import org.ocpsoft.rewrite.context.Context; import com.tinkerpop.blueprints.Vertex; /** * Contains all {@link IssueCategory} objects that have been registered by Windup. * * @author <a href="mailto:jesse.sightler@gmail.com">Jesse Sightler</a> */ public class IssueCategoryRegistry { public static final String MANDATORY = "mandatory"; public static final String OPTIONAL = "optional"; public static final String POTENTIAL = "potential"; public static final String CLOUD_MANDATORY = "cloud-mandatory"; public static final String DEFAULT = OPTIONAL; private Map<String, IssueCategory> issueCategories = new ConcurrentHashMap<>(); /** * Gets an instance of the {@link IssueCategoryRegistry} from a {@link Context}. Note that the context variable * used might vary depending upon how this class is being used. * * In some cases, the Context might be a part of the {@link RuleLoaderContext}, so that this registry can be used * during rule initialization. In other cases, it may be the {@link GraphRewrite} event's context. */ public static IssueCategoryRegistry instance(Context context) { IssueCategoryRegistry registry = (IssueCategoryRegistry) context.get(IssueCategoryRegistry.class); if (registry == null) { registry = new IssueCategoryRegistry(); context.put(IssueCategoryRegistry.class, registry); } return registry; } /** * Create a new {@link IssueCategoryRegistry}. */ public IssueCategoryRegistry() { // Add some default values as placeholders. These will generally be further defined by the rulesets themselves. addDefaults(); } /** * Attaches all registered {@link IssueCategory}s to the graph. This allows them to be more easily used * from rules. */ public void attachToGraph(GraphContext graphContext) { for (IssueCategory issueCategory : this.issueCategories.values()) { IssueCategoryModel model = graphContext.create(IssueCategoryModel.class); model.setCategoryID(issueCategory.getCategoryID()); model.setName(issueCategory.getName()); model.setDescription(issueCategory.getDescription()); model.setOrigin(issueCategory.getOrigin()); model.setPriority(issueCategory.getPriority()); } } /** * Loads the related graph vertex for the given {@link IssueCategory#getCategoryID()}. */ public static IssueCategoryModel loadFromGraph(GraphContext graphContext, String issueCategoryID) { return loadFromGraph(graphContext.getFramed(), issueCategoryID); } /** * Loads the related graph vertex for the given {@link IssueCategory#getCategoryID()}. */ public static IssueCategoryModel loadFromGraph(FramedGraph<EventGraph<TitanGraph>> framedGraph, String issueCategoryID) { Iterable<Vertex> vertices = framedGraph.query().has(WindupVertexFrame.TYPE_PROP, IssueCategoryModel.TYPE) .has(IssueCategoryModel.CATEGORY_ID, issueCategoryID).vertices(); IssueCategoryModel result = null; for (Vertex vertex : vertices) { if (result != null) throw new DuplicateIssueCategoryException("Found more than one issue category for this id: " + issueCategoryID); result = framedGraph.frame(vertex, IssueCategoryModel.class); } return result; } /** * Loads the related graph vertex for the given {@link IssueCategory}. */ public static IssueCategoryModel loadFromGraph(GraphContext graphContext, IssueCategory issueCategory) { return loadFromGraph(graphContext.getFramed(), issueCategory.getCategoryID()); } /** * Adds a {@link IssueCategory} to the registry and throws a {@link DuplicateIssueCategoryException} if the item already exists. */ public void addCategory(IssueCategory category) throws DuplicateIssueCategoryException { IssueCategory original = this.issueCategories.get(category.getCategoryID()); // Just overwrite it if it was a placeholder if (original != null && !original.isPlaceholder()) { StringBuilder message = new StringBuilder("Issue category (ID: ").append(category.getCategoryID()) .append(") is defined at the following locations:"); message.append(OperatingSystemUtils.getLineSeparator()); message.append("\t1: " + original.getOrigin()); message.append("\t2: " + category.getOrigin()); throw new DuplicateIssueCategoryException(message.toString()); } this.issueCategories.put(category.getCategoryID(), category); } /** * Gets an {@link IssueCategory} by ID. */ public IssueCategory getByID(String categoryID) { IssueCategory issueCategory = this.issueCategories.get(categoryID); if (issueCategory == null) { // We do not have this one yet, so store it as a placeholder. It will presumably be loaded later on. issueCategory = new IssueCategory(categoryID, "placeholder", categoryID, categoryID, 0, true); this.issueCategories.put(categoryID, issueCategory); } return issueCategory; } /** * Returns a list ordered from the highest priority to the lowest. */ public List<IssueCategory> getIssueCategories() { return this.issueCategories.values().stream() .sorted((category1, category2) -> category1.getPriority() - category2.getPriority()) .collect(Collectors.toList()); } /** * Make sure that we have some reasonable defaults available. These would typically be provided by the rulesets * in the real world. */ private void addDefaults() { this.issueCategories.putIfAbsent(MANDATORY, new IssueCategory(MANDATORY, IssueCategoryRegistry.class.getCanonicalName(), "Mandatory", MANDATORY, 1000, true)); this.issueCategories.putIfAbsent(OPTIONAL, new IssueCategory(OPTIONAL, IssueCategoryRegistry.class.getCanonicalName(), "Optional", OPTIONAL, 1000, true)); this.issueCategories.putIfAbsent(POTENTIAL, new IssueCategory(POTENTIAL, IssueCategoryRegistry.class.getCanonicalName(), "Potential Issues", POTENTIAL, 1000, true)); } }