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}