001/* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2008-2012 SonarSource 004 * mailto:contact AT sonarsource DOT com 005 * 006 * Sonar is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * Sonar is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with Sonar; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 019 */ 020package org.sonar.core.workflow; 021 022import com.google.common.base.Preconditions; 023import com.google.common.base.Strings; 024import com.google.common.collect.ArrayListMultimap; 025import com.google.common.collect.ImmutableMap; 026import com.google.common.collect.ListMultimap; 027import com.google.common.collect.Lists; 028import org.sonar.api.ServerComponent; 029import org.sonar.api.config.Settings; 030import org.sonar.api.workflow.Review; 031import org.sonar.api.workflow.WorkflowContext; 032import org.sonar.api.workflow.condition.Condition; 033import org.sonar.api.workflow.function.Function; 034import org.sonar.api.workflow.internal.DefaultReview; 035import org.sonar.api.workflow.internal.DefaultWorkflow; 036import org.sonar.api.workflow.internal.DefaultWorkflowContext; 037import org.sonar.api.workflow.screen.Screen; 038 039import javax.annotation.Nullable; 040import java.util.List; 041import java.util.Map; 042 043public class WorkflowEngine implements ServerComponent { 044 045 private final DefaultWorkflow workflow; 046 private final ReviewStore store; 047 private final Settings settings; 048 049 public WorkflowEngine(DefaultWorkflow workflow, ReviewStore store, Settings settings) { 050 this.workflow = workflow; 051 this.store = store; 052 this.settings = settings; 053 } 054 055 /** 056 * @return non-null list of screens per review#violationId 057 */ 058 public ListMultimap<Long, Screen> listAvailableScreens(DefaultReview[] reviews, DefaultWorkflowContext context, boolean verifyConditions) { 059 ListMultimap<Long, Screen> result = ArrayListMultimap.create(); 060 061 completeProjectSettings(context); 062 063 for (Map.Entry<String, Screen> entry : workflow.getScreensByCommand().entrySet()) { 064 String commandKey = entry.getKey(); 065 if (!verifyConditions || verifyConditionsQuietly(null, context, workflow.getContextConditions(commandKey))) { 066 for (DefaultReview review : reviews) { 067 if (!verifyConditions || verifyConditionsQuietly(review, context, workflow.getReviewConditions(commandKey))) { 068 result.put(review.getViolationId(), entry.getValue()); 069 } 070 } 071 } 072 } 073 return result; 074 } 075 076 public List<Screen> listAvailableScreens(Review review, DefaultWorkflowContext context, boolean verifyConditions) { 077 List<Screen> result = Lists.newArrayList(); 078 completeProjectSettings(context); 079 for (Map.Entry<String, Screen> entry : workflow.getScreensByCommand().entrySet()) { 080 String commandKey = entry.getKey(); 081 if (!verifyConditions || verifyConditionsQuietly(review, context, workflow.getConditions(commandKey))) { 082 result.add(entry.getValue()); 083 084 } 085 } 086 return result; 087 } 088 089 /** 090 * @return the optional (nullable) screen associated to the command 091 */ 092 public Screen getScreen(String commandKey) { 093 return workflow.getScreen(commandKey); 094 } 095 096 public void execute(String commandKey, DefaultReview review, DefaultWorkflowContext context, Map<String, String> parameters) { 097 Preconditions.checkArgument(!Strings.isNullOrEmpty(commandKey), "Missing command"); 098 Preconditions.checkArgument(workflow.hasCommand(commandKey), "Unknown command: " + commandKey); 099 100 completeProjectSettings(context); 101 102 verifyConditions(review, context, workflow.getConditions(commandKey)); 103 104 Map<String, String> immutableParameters = ImmutableMap.copyOf(parameters); 105 106 // TODO execute functions are change state before functions that consume state (like "create-jira-issue") 107 Review initialReview = new ImmutableReview(review); 108 for (Function function : workflow.getFunctions(commandKey)) { 109 function.doExecute(review, initialReview, context, immutableParameters); 110 } 111 112 // should it be extracted to a core function ? 113 store.store(review); 114 115 // TODO notify listeners 116 } 117 118 private boolean verifyConditionsQuietly(@Nullable Review review, WorkflowContext context, List<Condition> conditions) { 119 for (Condition condition : conditions) { 120 if (!condition.doVerify(review, context)) { 121 return false; 122 } 123 } 124 return true; 125 } 126 127 private void verifyConditions(@Nullable Review review, WorkflowContext context, List<Condition> conditions) { 128 for (Condition condition : conditions) { 129 if (!condition.doVerify(review, context)) { 130 throw new IllegalStateException("Condition is not respected: " + condition.toString()); 131 } 132 } 133 } 134 135 private void completeProjectSettings(DefaultWorkflowContext context) { 136 Settings projectSettings = new Settings(settings); 137 List<String> propertyKeys = workflow.getProjectPropertyKeys(); 138 store.completeProjectSettings(context.getProjectId(), projectSettings, propertyKeys); 139 context.setSettings(projectSettings); 140 } 141}