Commits

Penny Wyatt [Atlassian] committed 2e2bfb8

Removed everything JIRA-specific from the generic services and exported them as components.

Comments (0)

Files changed (9)

-<?xml version="1.0" encoding="UTF-8"?>
-
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-
-	<modelVersion>4.0.0</modelVersion>
-	<groupId>com.atlassian.qa</groupId>
-	<artifactId>jira-qa-helper</artifactId>
-	<version>1.4-SNAPSHOT</version>
-
-	<organization>
-		<name>Atlassian</name>
-		<url>http://www.atlassian.com/</url>
-	</organization>
-
-	<name>jira-qa-helper</name>
-	<description>Plugin that provides checkin-based QA testing hints for JIRA</description>
-	<packaging>atlassian-plugin</packaging>
-
-	<dependencies>
-		<dependency>
-			<groupId>com.atlassian.jira</groupId>
-			<artifactId>jira-api</artifactId>
-			<version>${jira.version}</version>
-			<scope>provided</scope>
-		</dependency>
-		<!-- Required for Wiki Renderer -->
-		<dependency>
-			<groupId>com.atlassian.jira</groupId>
-			<artifactId>jira-core</artifactId>
-			<version>${jira.version}</version>
-			<scope>provided</scope>
-		</dependency>
-		<dependency>
-			<groupId>junit</groupId>
-			<artifactId>junit</artifactId>
-			<version>4.8.1</version>
-			<scope>test</scope>
-		</dependency>
-		<dependency>
-			<groupId>com.atlassian.jira</groupId>
-			<artifactId>jira-tests</artifactId>
-			<version>${jira.version}</version>
-			<scope>test</scope>
-		</dependency>
-		<dependency>
-			<groupId>com.atlassian.jira</groupId>
-			<artifactId>jira-func-tests</artifactId>
-			<version>${jira.version}</version>
-			<scope>test</scope>
-		</dependency>
-		<dependency>
-			<groupId>com.atlassian.sal</groupId>
-			<artifactId>sal-api</artifactId>
-			<version>2.7.1</version>
-			<scope>provided</scope>
-		</dependency>
-		<dependency>
-			<groupId>com.atlassian.applinks</groupId>
-			<artifactId>applinks-plugin</artifactId>
-			<version>3.9.0</version>
-			<scope>provided</scope>
-		</dependency>
-		<dependency>
-			<groupId>javax.servlet</groupId>
-			<artifactId>servlet-api</artifactId>
-			<version>2.4</version>
-			<scope>provided</scope>
-		</dependency>
-	</dependencies>
-
-	<build>
-		<plugins>
-			<plugin>
-				<groupId>com.atlassian.maven.plugins</groupId>
-				<artifactId>maven-jira-plugin</artifactId>
-				<version>3.7.2</version>
-				<extensions>true</extensions>
-				<configuration>
-					<productVersion>${jira.version}</productVersion>
-					<productDataVersion>${jira.version}</productDataVersion>
-					<jvmArgs>-Xmx512m -XX:MaxPermSize=256m</jvmArgs>
-				</configuration>
-			</plugin>
-			<plugin>
-				<artifactId>maven-compiler-plugin</artifactId>
-				<configuration>
-					<source>1.6</source>
-					<target>1.6</target>
-				</configuration>
-			</plugin>
-		</plugins>
-	</build>
-
-	<properties>
-		<jira.version>5.2-m04</jira.version>
-		<amps.version>3.7.2</amps.version>
-	</properties>
-
-    <scm>
-        <connection>scm:git:ssh://git@bitbucket.org/pwyatt/jira-qa-helper.git</connection>
-        <developerConnection>scm:git:ssh://git@bitbucket.org/pwyatt/jira-qa-helper.git</developerConnection>
-        <url>https://bitbucket.org/pwyatt/jira-qa-helper/</url>
-    </scm>
-    
-    <distributionManagement>
-        <repository>
-            <id>atlassian-private</id>
-            <name>Atlassian Private Repository</name>
-            <url>https://maven.atlassian.com/private</url>
-        </repository>
-        <snapshotRepository>
-            <id>atlassian-private-snapshot</id>
-            <name>Atlassian Private Snapshot Repository</name>
-            <url>https://maven.atlassian.com/private-snapshot</url>
-        </snapshotRepository>
-    </distributionManagement>    
-</project>
+<?xml version="1.0" encoding="UTF-8"?>
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+	<modelVersion>4.0.0</modelVersion>
+	<groupId>com.atlassian.qa</groupId>
+	<artifactId>jira-qa-helper</artifactId>
+	<version>1.4-SNAPSHOT</version>
+
+	<organization>
+		<name>Atlassian</name>
+		<url>http://www.atlassian.com/</url>
+	</organization>
+
+	<name>jira-qa-helper</name>
+	<description>Plugin that provides checkin-based QA testing hints for JIRA</description>
+	<packaging>atlassian-plugin</packaging>
+
+	<dependencies>
+		<dependency>
+			<groupId>com.atlassian.jira</groupId>
+			<artifactId>jira-api</artifactId>
+			<version>${jira.version}</version>
+			<scope>provided</scope>
+		</dependency>
+		<!-- Required for Wiki Renderer -->
+		<dependency>
+			<groupId>com.atlassian.jira</groupId>
+			<artifactId>jira-core</artifactId>
+			<version>${jira.version}</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<version>4.8.1</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>com.atlassian.jira</groupId>
+			<artifactId>jira-tests</artifactId>
+			<version>${jira.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>com.atlassian.jira</groupId>
+			<artifactId>jira-func-tests</artifactId>
+			<version>${jira.version}</version>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>com.atlassian.sal</groupId>
+			<artifactId>sal-api</artifactId>
+			<version>2.7.1</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>com.atlassian.applinks</groupId>
+			<artifactId>applinks-plugin</artifactId>
+			<version>3.9.0</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>javax.servlet</groupId>
+			<artifactId>servlet-api</artifactId>
+			<version>2.4</version>
+			<scope>provided</scope>
+		</dependency>
+	</dependencies>
+
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>com.atlassian.maven.plugins</groupId>
+				<artifactId>maven-jira-plugin</artifactId>
+				<version>3.7.2</version>
+				<extensions>true</extensions>
+				<configuration>
+					<productVersion>${jira.version}</productVersion>
+					<productDataVersion>${jira.version}</productDataVersion>
+					<jvmArgs>-Xmx512m -XX:MaxPermSize=256m</jvmArgs>
+				</configuration>
+			</plugin>
+			<plugin>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<source>1.6</source>
+					<target>1.6</target>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+
+	<properties>
+		<jira.version>5.2-m04</jira.version>
+		<amps.version>3.7.2</amps.version>
+	</properties>
+
+    <scm>
+        <connection>scm:git:ssh://git@bitbucket.org/pwyatt/jira-qa-helper.git</connection>
+        <developerConnection>scm:git:ssh://git@bitbucket.org/pwyatt/jira-qa-helper.git</developerConnection>
+        <url>https://bitbucket.org/pwyatt/jira-qa-helper/</url>
+    </scm>
+    
+    <distributionManagement>
+        <repository>
+            <id>atlassian-private</id>
+            <name>Atlassian Private Repository</name>
+            <url>https://maven.atlassian.com/private</url>
+        </repository>
+        <snapshotRepository>
+            <id>atlassian-private-snapshot</id>
+            <name>Atlassian Private Snapshot Repository</name>
+            <url>https://maven.atlassian.com/private-snapshot</url>
+        </snapshotRepository>
+    </distributionManagement>    
+</project>

src/main/java/com/atlassianqa/jiraqahelper/services/rules/RuleService.java

 package com.atlassianqa.jiraqahelper.services.rules;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 
-import org.codehaus.jackson.JsonNode;
-import org.codehaus.jackson.map.ObjectMapper;
-
-import com.atlassian.jira.project.Project;
 import com.atlassianqa.jiraqahelper.services.settings.PluginSettingsService;
 
-public class RuleService 
+public interface RuleService 
 {
-	private Map<String, List<Rule>> rulesMap = null;
-    
-    public RuleService (PluginSettingsService pluginSettingsService)
-    {
-        String rulesJson = pluginSettingsService.getRulesJson();
-        if (rulesJson != null)
-        {
-            this.rulesMap = parseRules (rulesJson);
-        }
-    }
-    
-    private Map<String, List<Rule>> parseRules(String rulesJson) 
-    {
-    	Map<String, List<Rule>> rulesMap = new HashMap<String, List<Rule>> ();
-        
-        ObjectMapper mapper = new ObjectMapper();
-        try 
-        {
-            JsonNode rulesData = mapper.readValue(rulesJson, JsonNode.class);
-            Iterator<JsonNode> projectNodes = rulesData.path("projects").getElements();
-            while (projectNodes.hasNext())
-            {
-            	JsonNode projectNode = projectNodes.next();
-	        	List<Rule> rules = new ArrayList<Rule> ();
-	            String projectKey = projectNode.path("key").getTextValue();
-	            Iterator<JsonNode> rulesNodes = projectNode.path("rules").getElements();
-	            while (rulesNodes.hasNext())
-	            {
-	                JsonNode ruleNode = rulesNodes.next();
-	                List<MatchingRule> matchingRules = new ArrayList<MatchingRule> ();
-	                Iterator<JsonNode> matchingRulesNodes = ruleNode.path("matching").getElements();
-	                while (matchingRulesNodes.hasNext())
-	                {
-	                    JsonNode matchingRuleNode = matchingRulesNodes.next();
-	                    boolean invert = false;
-	                    if (matchingRuleNode.path("invert") != null)
-	                    {
-	                        invert = Boolean.parseBoolean(matchingRuleNode.path("invert").getTextValue());
-	                    }
-	                    matchingRules.add (new MatchingRule (matchingRuleNode.path("property").getTextValue(),
-	                                                         matchingRuleNode.path("regex").getTextValue(),
-	                                                         invert));
-	                }
-	                
-	                Rule rule = new Rule (ruleNode.path("name").getTextValue(),
-	                                      ruleNode.path("specificity").getTextValue(),
-	                                      ruleNode.path("description").getTextValue(),
-	                                      matchingRules);
-	                rules.add (rule);
-	            }
-	            rulesMap.put(projectKey, rules);
-            }
-            
-        } catch (IOException e) 
-        {
-            return null;
-        }
-        
-        return rulesMap;
-    }
-
-    public List<Rule> getRules (Project project)
-    {
-    	return getRules (project.getKey());
-    }
+    public void refreshRulesFromSettings (PluginSettingsService pluginSettingsService);
     
-    public List<Rule> getRules (String projectKey)
-    {
-        if (this.rulesMap == null)
-        {
-            return null;
-        }
-        return this.rulesMap.get (projectKey);
-    }
+    public List<Rule> getRulesForProject (String projectKey);
 }

src/main/java/com/atlassianqa/jiraqahelper/services/rules/RuleServiceImpl.java

+package com.atlassianqa.jiraqahelper.services.rules;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.map.ObjectMapper;
+
+import com.atlassianqa.jiraqahelper.services.settings.PluginSettingsService;
+
+public class RuleServiceImpl implements RuleService
+{
+	private Map<String, List<Rule>> rulesMap = null;
+    
+    public RuleServiceImpl (PluginSettingsService pluginSettingsService)
+    {
+        refreshRulesFromSettings (pluginSettingsService);
+    }
+    
+    public void refreshRulesFromSettings (PluginSettingsService pluginSettingsService)
+    {
+        String rulesJson = pluginSettingsService.getRulesJson();
+        if (rulesJson != null)
+        {
+            this.rulesMap = parseRules (rulesJson);
+        }
+    }
+    
+    public List<Rule> getRulesForProject (String projectKey)
+    {
+        if (this.rulesMap == null)
+        {
+            return null;
+        }
+        return this.rulesMap.get (projectKey);
+    }
+    
+    private Map<String, List<Rule>> parseRules(String rulesJson) 
+    {
+    	Map<String, List<Rule>> rulesMap = new HashMap<String, List<Rule>> ();
+        
+        ObjectMapper mapper = new ObjectMapper();
+        try 
+        {
+            JsonNode rulesData = mapper.readValue(rulesJson, JsonNode.class);
+            Iterator<JsonNode> projectNodes = rulesData.path("projects").getElements();
+            while (projectNodes.hasNext())
+            {
+            	JsonNode projectNode = projectNodes.next();
+	        	List<Rule> rules = new ArrayList<Rule> ();
+	            String projectKey = projectNode.path("key").getTextValue();
+	            Iterator<JsonNode> rulesNodes = projectNode.path("rules").getElements();
+	            while (rulesNodes.hasNext())
+	            {
+	                JsonNode ruleNode = rulesNodes.next();
+	                List<MatchingRule> matchingRules = new ArrayList<MatchingRule> ();
+	                Iterator<JsonNode> matchingRulesNodes = ruleNode.path("matching").getElements();
+	                while (matchingRulesNodes.hasNext())
+	                {
+	                    JsonNode matchingRuleNode = matchingRulesNodes.next();
+	                    boolean invert = false;
+	                    if (matchingRuleNode.path("invert") != null)
+	                    {
+	                        invert = Boolean.parseBoolean(matchingRuleNode.path("invert").getTextValue());
+	                    }
+	                    matchingRules.add (new MatchingRule (matchingRuleNode.path("property").getTextValue(),
+	                                                         matchingRuleNode.path("regex").getTextValue(),
+	                                                         invert));
+	                }
+	                
+	                Rule rule = new Rule (ruleNode.path("name").getTextValue(),
+	                                      ruleNode.path("specificity").getTextValue(),
+	                                      ruleNode.path("description").getTextValue(),
+	                                      matchingRules);
+	                rules.add (rule);
+	            }
+	            rulesMap.put(projectKey, rules);
+            }
+            
+        } catch (IOException e) 
+        {
+            return null;
+        }
+        
+        return rulesMap;
+    }
+}

src/main/java/com/atlassianqa/jiraqahelper/services/settings/PluginSettingsService.java

 package com.atlassianqa.jiraqahelper.services.settings;
 
-public interface PluginSettingsService {
-
+public interface PluginSettingsService 
+{
     public String getRulesJson ();
     
     public String getErrorString();

src/main/java/com/atlassianqa/jiraqahelper/services/stash/StashService.java

 package com.atlassianqa.jiraqahelper.services.stash;
 
-import com.atlassian.applinks.api.ApplicationLink;
-import com.atlassian.applinks.api.ApplicationLinkRequest;
-import com.atlassian.applinks.api.ApplicationLinkRequestFactory;
-import com.atlassian.applinks.api.ApplicationLinkResponseHandler;
-import com.atlassian.applinks.api.ApplicationLinkService;
-import com.atlassian.applinks.api.CredentialsRequiredException;
-import com.atlassian.applinks.api.application.stash.StashApplicationType;
-import com.atlassian.sal.api.net.Request;
-import com.atlassian.sal.api.net.Response;
-import com.atlassian.sal.api.net.ResponseException;
 import com.atlassianqa.jiraqahelper.services.commits.CommitDiffData;
 import com.atlassianqa.jiraqahelper.services.commits.FileDiff;
 import com.atlassianqa.jiraqahelper.services.commits.IssueCommitData;
 
-import org.codehaus.jackson.JsonNode;
-import org.codehaus.jackson.map.ObjectMapper;
-import org.jfree.util.Log;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.concurrent.atomic.AtomicReference;
+public interface StashService 
+{
+    public boolean isConfigured();
 
-public class StashService {
+    public CommitDiffData getDiffContent (FileDiff fileDiff) throws StashException;
 
-    // The number of commits to retrieve per issue
-    public static final int MAX_COMMITS = 500;
-    // The number of changes to retrieve per commit
-    public static final int MAX_CHANGES = 500;
-
-    private ApplicationLinkService applicationLinkService;
-
-    public StashService (final ApplicationLinkService applicationLinkService) 
-    {
-        this.applicationLinkService = applicationLinkService;
-    }
-    
-	public boolean isConfigured() 
-	{
-		return (getStashLink(applicationLinkService) != null);
-	}
-	
-    public CommitDiffData getDiffContent (FileDiff fileDiff) throws StashException
-    {
-        String diffUrl = new StringBuilder ()
-            .append ("/rest/api/1.0/projects/").append (urlEncode(fileDiff.getProjectName()))
-            .append ("/repos/").append (urlEncode(fileDiff.getRepoName()))
-            .append ("/diff/").append (fileDiff.getFullPath())
-            .append ("?until=").append (urlEncode(fileDiff.getToCommitId()))
-            .append ("&since=").append (fileDiff.getFromCommitId() == null ? "" : urlEncode(fileDiff.getFromCommitId()))
-            .toString();
-        // Don't urlEncode the file path, because we need the slashes to actually stay as slashes
-        
-        JsonNode node = makeStashCall (diffUrl);
-        if (node == null) return null;
-        return new CommitDiffData (node);
-    }
-
-    public IssueCommitData getCommits (String issueKey) throws StashException
-    {
-        String commitsUrl = new StringBuilder("/rest/jira/1.0/issues/")
-            .append (urlEncode(issueKey)).append("/commits")
-            .append ("?maxChanges=").append(MAX_CHANGES)
-            .append ("&limit=").append(MAX_COMMITS)
-            .append ("&useBaseUrlToken=true")
-            .toString();
-        
-        JsonNode node = makeStashCall (commitsUrl);
-        if (node == null) return null;
-        return new IssueCommitData(node, getStashLink(applicationLinkService).getRpcUrl().toString());
-    }
-    
-    private String urlEncode (String value) throws StashConnectionFailedException
-    {
-        try 
-        {
-            return URLEncoder.encode(value, "UTF-8");
-        } 
-        catch (UnsupportedEncodingException e1) 
-        {
-            Log.error("Could not encode " + value + " as UTF-8");
-            throw new StashConnectionFailedException (e1.getMessage());
-        }
-    }
-    
-    private ApplicationLink getStashLink (ApplicationLinkService applicationLinkService)
-    {
-        
-        for (final ApplicationLink appLink : applicationLinkService.getApplicationLinks(StashApplicationType.class))
-        {
-            return appLink;
-        }
-        return null;
-    }
-    
-    private JsonNode makeStashCall (String url) throws StashException
-    {
-        ApplicationLink stashLink = getStashLink(applicationLinkService);
-        if (stashLink == null)
-        {
-            throw new NoStashAppLinkException ();
-        }
-        
-        final String fullUrl = stashLink.getRpcUrl() + url;
-        final AtomicReference<JsonNode> node = new AtomicReference<JsonNode> ();
-        final AtomicReference<StashException> exception = new AtomicReference<StashException> ();
-        try 
-        {
-            final ApplicationLinkRequestFactory requestFactory = stashLink.createAuthenticatedRequestFactory();
-            final ApplicationLinkRequest request = requestFactory.createRequest(Request.MethodType.GET, url);
-
-            // For some URLs, Stash returns HTML unless you specifically ask for JSON.
-            request.setHeader ("Accept", "application/json");
-            
-            // Make the call to Stash
-            request.execute(new ApplicationLinkResponseHandler<Void>() 
-            {
-                // TODO: For now, let people do the OAuth dance through the Source tab for now, rather than
-                // reimplementing it here
-                public Void credentialsRequired(final Response response) throws ResponseException 
-                {
-                    exception.set (new StashAuthenticationRequiredException(fullUrl));
-                    return null;
-                }
-
-                // Handle various response scenarios
-                public Void handle(final Response response) throws ResponseException 
-                {
-                    if (!response.isSuccessful()) 
-                    {
-                        exception.set (new StashConnectionFailedException(fullUrl + " " + response.getStatusText() + " " + response.getStatusCode() + " " + response.getResponseBodyAsString()));
-                        return null;
-                    }
-
-                    String resp = "";
-                    try 
-                    {
-                        // We got a string response, try to convert it to JSON and return it as a JSON node
-                        ObjectMapper mapper = new ObjectMapper();
-                        JsonNode commitData = mapper.readValue(response.getResponseBodyAsStream(), JsonNode.class);
-                        node.set (commitData);
-                    } 
-                    catch (IOException e) 
-                    {
-                        exception.set (new StashReadResponseFailedException (resp));
-                    }
-
-                    return null;
-                }
-            });
-        } 
-        catch (CredentialsRequiredException e) 
-        {
-            throw new StashAuthenticationRequiredException (fullUrl);
-        } 
-        catch (ResponseException e) 
-        {
-            throw new StashReadResponseFailedException (fullUrl);
-        }
-        
-        if (exception.get() != null)
-        {
-            throw exception.get();
-        }
-
-        return node.get();
-    }
-
-}
+    public IssueCommitData getCommits (String issueKey) throws StashException;
+}

src/main/java/com/atlassianqa/jiraqahelper/services/stash/StashServiceImpl.java

+package com.atlassianqa.jiraqahelper.services.stash;
+
+import com.atlassian.applinks.api.ApplicationLink;
+import com.atlassian.applinks.api.ApplicationLinkRequest;
+import com.atlassian.applinks.api.ApplicationLinkRequestFactory;
+import com.atlassian.applinks.api.ApplicationLinkResponseHandler;
+import com.atlassian.applinks.api.ApplicationLinkService;
+import com.atlassian.applinks.api.CredentialsRequiredException;
+import com.atlassian.applinks.api.application.stash.StashApplicationType;
+import com.atlassian.sal.api.net.Request;
+import com.atlassian.sal.api.net.Response;
+import com.atlassian.sal.api.net.ResponseException;
+import com.atlassianqa.jiraqahelper.services.commits.CommitDiffData;
+import com.atlassianqa.jiraqahelper.services.commits.FileDiff;
+import com.atlassianqa.jiraqahelper.services.commits.IssueCommitData;
+
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.jfree.util.Log;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class StashServiceImpl implements StashService {
+
+    // The number of commits to retrieve per issue
+    public static final int MAX_COMMITS = 500;
+    // The number of changes to retrieve per commit
+    public static final int MAX_CHANGES = 500;
+
+    private ApplicationLinkService applicationLinkService;
+
+    public StashServiceImpl (final ApplicationLinkService applicationLinkService) 
+    {
+        this.applicationLinkService = applicationLinkService;
+    }
+    
+	public boolean isConfigured() 
+	{
+		return (getStashLink(applicationLinkService) != null);
+	}
+	
+    public CommitDiffData getDiffContent (FileDiff fileDiff) throws StashException
+    {
+        String diffUrl = new StringBuilder ()
+            .append ("/rest/api/1.0/projects/").append (urlEncode(fileDiff.getProjectName()))
+            .append ("/repos/").append (urlEncode(fileDiff.getRepoName()))
+            .append ("/diff/").append (fileDiff.getFullPath())
+            .append ("?until=").append (urlEncode(fileDiff.getToCommitId()))
+            .append ("&since=").append (fileDiff.getFromCommitId() == null ? "" : urlEncode(fileDiff.getFromCommitId()))
+            .toString();
+        // Don't urlEncode the file path, because we need the slashes to actually stay as slashes
+        
+        JsonNode node = makeStashCall (diffUrl);
+        if (node == null) return null;
+        return new CommitDiffData (node);
+    }
+
+    public IssueCommitData getCommits (String issueKey) throws StashException
+    {
+        String commitsUrl = new StringBuilder("/rest/jira/1.0/issues/")
+            .append (urlEncode(issueKey)).append("/commits")
+            .append ("?maxChanges=").append(MAX_CHANGES)
+            .append ("&limit=").append(MAX_COMMITS)
+            .append ("&useBaseUrlToken=true")
+            .toString();
+        
+        JsonNode node = makeStashCall (commitsUrl);
+        if (node == null) return null;
+        return new IssueCommitData(node, getStashLink(applicationLinkService).getRpcUrl().toString());
+    }
+    
+    private String urlEncode (String value) throws StashConnectionFailedException
+    {
+        try 
+        {
+            return URLEncoder.encode(value, "UTF-8");
+        } 
+        catch (UnsupportedEncodingException e1) 
+        {
+            Log.error("Could not encode " + value + " as UTF-8");
+            throw new StashConnectionFailedException (e1.getMessage());
+        }
+    }
+    
+    private ApplicationLink getStashLink (ApplicationLinkService applicationLinkService)
+    {
+        
+        for (final ApplicationLink appLink : applicationLinkService.getApplicationLinks(StashApplicationType.class))
+        {
+            return appLink;
+        }
+        return null;
+    }
+    
+    private JsonNode makeStashCall (String url) throws StashException
+    {
+        ApplicationLink stashLink = getStashLink(applicationLinkService);
+        if (stashLink == null)
+        {
+            throw new NoStashAppLinkException ();
+        }
+        
+        final String fullUrl = stashLink.getRpcUrl() + url;
+        final AtomicReference<JsonNode> node = new AtomicReference<JsonNode> ();
+        final AtomicReference<StashException> exception = new AtomicReference<StashException> ();
+        try 
+        {
+            final ApplicationLinkRequestFactory requestFactory = stashLink.createAuthenticatedRequestFactory();
+            final ApplicationLinkRequest request = requestFactory.createRequest(Request.MethodType.GET, url);
+
+            // For some URLs, Stash returns HTML unless you specifically ask for JSON.
+            request.setHeader ("Accept", "application/json");
+            
+            // Make the call to Stash
+            request.execute(new ApplicationLinkResponseHandler<Void>() 
+            {
+                // TODO: For now, let people do the OAuth dance through the Source tab for now, rather than
+                // reimplementing it here
+                public Void credentialsRequired(final Response response) throws ResponseException 
+                {
+                    exception.set (new StashAuthenticationRequiredException(fullUrl));
+                    return null;
+                }
+
+                // Handle various response scenarios
+                public Void handle(final Response response) throws ResponseException 
+                {
+                    if (!response.isSuccessful()) 
+                    {
+                        exception.set (new StashConnectionFailedException(fullUrl + " " + response.getStatusText() + " " + response.getStatusCode() + " " + response.getResponseBodyAsString()));
+                        return null;
+                    }
+
+                    String resp = "";
+                    try 
+                    {
+                        // We got a string response, try to convert it to JSON and return it as a JSON node
+                        ObjectMapper mapper = new ObjectMapper();
+                        JsonNode commitData = mapper.readValue(response.getResponseBodyAsStream(), JsonNode.class);
+                        node.set (commitData);
+                    } 
+                    catch (IOException e) 
+                    {
+                        exception.set (new StashReadResponseFailedException (resp));
+                    }
+
+                    return null;
+                }
+            });
+        } 
+        catch (CredentialsRequiredException e) 
+        {
+            throw new StashAuthenticationRequiredException (fullUrl);
+        } 
+        catch (ResponseException e) 
+        {
+            throw new StashReadResponseFailedException (fullUrl);
+        }
+        
+        if (exception.get() != null)
+        {
+            throw exception.get();
+        }
+
+        return node.get();
+    }
+
+}

src/main/java/com/atlassianqa/jiraqahelper/ui/HelperTabPanel.java

 
 import java.util.List;
 
-import com.atlassian.applinks.api.ApplicationLinkService;
 import com.atlassian.crowd.embedded.api.User;
 import com.atlassian.jira.issue.Issue;
 import com.atlassian.jira.issue.RendererManager;
 {
     protected IssueTabPanelModuleDescriptor descriptor;
     private StashService stashService;
-    private RendererManager rendererManager;
-    private PluginSettingsService pluginSettingsService;
+    private RuleService ruleService;
     private HintService hintService;
+    private PluginSettingsService pluginSettingsService;    
+    private RendererManager rendererManager;
     
-    public HelperTabPanel (ApplicationLinkService applicationLinkService, RendererManager rendererManager, PluginSettingsService pluginSettingsService, HintService hintService)
+    public HelperTabPanel (StashService stashService, HintService hintService, RuleService ruleService, PluginSettingsService pluginSettingsService, RendererManager rendererManager)
     {
-        this.stashService = new StashService(applicationLinkService);
-        this.rendererManager = rendererManager;
-        this.pluginSettingsService = pluginSettingsService;
+        this.stashService = stashService;
+        this.ruleService = ruleService;
         this.hintService = hintService;
+        this.pluginSettingsService = pluginSettingsService;
+        this.rendererManager = rendererManager;
     }
     
     public void init (IssueTabPanelModuleDescriptor descriptor) {
         actions.add (new HelperTabPanelHeaderAction (descriptor, issue.getKey()));
 
         // Get the hints from the plugin settings for this project.
-        List<Rule> rules = new RuleService (pluginSettingsService).getRules (issue.getProjectObject());
+        List<Rule> rules = ruleService.getRulesForProject (issue.getProjectObject().getKey());
         if (rules == null || rules.size() == 0) {
             actions.add (new HelperTabPanelErrorAction (descriptor, "jiraqahelper.tabpanel.error.norules", null, true));
             return actions;
                 if (stashService.isConfigured ())
                 {
                     // ... and if rules have been configured for this project.
-                    List<Rule> rules = new RuleService (pluginSettingsService).getRules(issue.getProjectObject());
+                    List<Rule> rules = ruleService.getRulesForProject (issue.getProjectObject().getKey());
                     return (rules != null && rules.size() > 0);
                 }
             }

src/main/java/com/atlassianqa/jiraqahelper/ui/UploadDataAction.java

 import com.atlassian.jira.security.Permissions;
 import com.atlassian.jira.security.xsrf.RequiresXsrfCheck;
 import com.atlassian.jira.web.action.JiraWebActionSupport;
+import com.atlassianqa.jiraqahelper.services.rules.RuleService;
 import com.atlassianqa.jiraqahelper.services.settings.PluginSettingsService;
 
 import java.io.File;
     private static final String FILE_FORM_KEY = "datafile";
     private static final String USER_FORM_KEY = "allowedusers";
     private PluginSettingsService pluginSettingsService;
+    private RuleService ruleService;
     
-    public UploadDataAction (PluginSettingsService pluginSettingsService) 
+    public UploadDataAction (PluginSettingsService pluginSettingsService, RuleService ruleService) 
     {
         this.pluginSettingsService = pluginSettingsService;
+        this.ruleService = ruleService;
     }
 
     public String doDefault () throws Exception 
         {
             String dataFileContent = new Scanner(uploadedFile).useDelimiter("\\Z").next();
             pluginSettingsService.setRulesJson (dataFileContent);
+            ruleService.refreshRulesFromSettings(pluginSettingsService);
             pluginSettingsService.setErrorString (getValidationError(dataFileContent));
         }
         

src/main/resources/atlassian-plugin.xml

         <vendor name="${project.organization.name}" url="${project.organization.url}" />
     </plugin-info>
     
-    <component-import key="applicationLinkService">
-	   <interface>com.atlassian.applinks.api.ApplicationLinkService</interface>
-	</component-import>
-    <component-import key="i18nResolver" interface="com.atlassian.sal.api.message.I18nResolver"/>
-    <component-import key="pluginSettingsFactory" interface="com.atlassian.sal.api.pluginsettings.PluginSettingsFactory" />
-    <component-import key="transactionTemplate" interface="com.atlassian.sal.api.transaction.TransactionTemplate" />
-	<component-import key="jiraThreadLocalDelegateExecutorFactory" interface="com.atlassian.sal.api.executor.ThreadLocalDelegateExecutorFactory" />
+    <!-- Component Imports -->
+    
+        <component-import key="applicationLinkService">
+    	   <interface>com.atlassian.applinks.api.ApplicationLinkService</interface>
+    	</component-import>
+        <component-import key="i18nResolver" interface="com.atlassian.sal.api.message.I18nResolver"/>
+        <component-import key="pluginSettingsFactory" interface="com.atlassian.sal.api.pluginsettings.PluginSettingsFactory" />
+        <component-import key="transactionTemplate" interface="com.atlassian.sal.api.transaction.TransactionTemplate" />
+    	<component-import key="jiraThreadLocalDelegateExecutorFactory" interface="com.atlassian.sal.api.executor.ThreadLocalDelegateExecutorFactory" />
 
-    <resource type="i18n" name="jiraqahelper-i18n" location="i18n.JIRAQAHelper"/>	
+    <!-- Common Services -->
 
-    <web-resource key="qa-helper-css" name="JIRA QA Helper CSS" >
-        <resource type="download" name="tabpanel.css" location="style/tabpanel.css" />
-        <context>jira.view.issue</context>
-    </web-resource>    
+        <component key="pluginSettingsService" name="Plugin Settings Service" class="com.atlassianqa.jiraqahelper.services.settings.PluginSettingsServiceImpl">
+            <interface>com.atlassianqa.jiraqahelper.services.settings.PluginSettingsService</interface>
+        </component>
 
-    <component key="pluginSettingsService" name="Plugin Settings Service" class="com.atlassianqa.jiraqahelper.services.settings.PluginSettingsServiceImpl">
-        <interface>com.atlassianqa.jiraqahelper.services.settings.PluginSettingsService</interface>
-    </component>
+        <component key="hintService" name="Hint Service" class="com.atlassianqa.jiraqahelper.services.hints.HintServiceImpl">
+            <interface>com.atlassianqa.jiraqahelper.services.hints.HintService</interface>
+        </component>
+    
+        <component key="stashService" name="Stash Service" class="com.atlassianqa.jiraqahelper.services.stash.StashServiceImpl">
+            <interface>com.atlassianqa.jiraqahelper.services.stash.StashService</interface>
+        </component>
+    
+        <component key="ruleService" name="Rule Service" class="com.atlassianqa.jiraqahelper.services.rules.RuleServiceImpl">
+            <interface>com.atlassianqa.jiraqahelper.services.rules.RuleService</interface>
+        </component>
+    
+    <!-- JIRA-Specific UI Components -->
+    
+        <issue-tabpanel key="qa-helper-tabpanel" name="JIRA QA Helper Tab Panel" class="com.atlassianqa.jiraqahelper.ui.HelperTabPanel">
+	        <description key="jiraqahelper.tabpanel.description" />
+	        <label key="jiraqahelper.tabpanel.label" />
+    	    <supports-ajax-load>true</supports-ajax-load>
+    	    <resource type="velocity" name="helper-tabpanel-header" location="templates/tabpanel/helper-tabpanel-header.vm"/>
+    	    <resource type="velocity" name="helper-tabpanel-hint" location="templates/tabpanel/helper-tabpanel-hint.vm"/>
+	        <resource type="velocity" name="helper-tabpanel-error" location="templates/tabpanel/helper-tabpanel-error.vm"/>
+    	    <resource type="velocity" name="helper-tabpanel-nonajax" location="templates/tabpanel/helper-tabpanel-nonajax.vm"/>
+    	</issue-tabpanel>
 
-    <component key="hintService" name="Hint Service" class="com.atlassianqa.jiraqahelper.services.hints.HintServiceImpl">
-        <interface>com.atlassianqa.jiraqahelper.services.hints.HintService</interface>
-    </component>
-    
-    <issue-tabpanel key="qa-helper-tabpanel" name="JIRA QA Helper Tab Panel" class="com.atlassianqa.jiraqahelper.ui.HelperTabPanel">
-	    <description key="jiraqahelper.tabpanel.description" />
-	    <label key="jiraqahelper.tabpanel.label" />
-	    <supports-ajax-load>true</supports-ajax-load>
-	    <resource type="velocity" name="helper-tabpanel-header" location="templates/tabpanel/helper-tabpanel-header.vm"/>
-	    <resource type="velocity" name="helper-tabpanel-hint" location="templates/tabpanel/helper-tabpanel-hint.vm"/>
-	    <resource type="velocity" name="helper-tabpanel-error" location="templates/tabpanel/helper-tabpanel-error.vm"/>
-	    <resource type="velocity" name="helper-tabpanel-nonajax" location="templates/tabpanel/helper-tabpanel-nonajax.vm"/>
-	</issue-tabpanel>
+        <webwork1 key="qa-helper-actions" name="JIRA QA Helper WebWork Actions" class="java.lang.Object">
+            <actions>	
+            	<action name="com.atlassianqa.jiraqahelper.ui.UploadDataAction" key="qa-helper-upload-data" alias="UploadDataAction">
+                    <view name="success">/templates/admin/helper-upload-data.vm</view>
+                    <view name="securitybreach">/secure/views/securitybreach.jsp</view>
+                </action>
+            </actions>
+        </webwork1>
+    
+        <web-item name="JIRA QA Helper Admin Link" key="jira-security-example-verify-identity" system="true" weight="49" section="system.admin/globalsettings">
+            <label key="jiraqahelper.admin.adminlink" />
+            <link linkId="jiraqahelper_data">/secure/admin/UploadDataAction!default.jspa</link>
+        </web-item>
+    
+        <web-resource key="qa-helper-css" name="JIRA QA Helper CSS" >
+            <resource type="download" name="tabpanel.css" location="style/tabpanel.css" />
+            <context>jira.view.issue</context>
+        </web-resource>    
 
-    <webwork1 key="qa-helper-actions" name="JIRA QA Helper WebWork Actions" class="java.lang.Object">
-        <actions>	
-        	<action name="com.atlassianqa.jiraqahelper.ui.UploadDataAction" key="qa-helper-upload-data" alias="UploadDataAction">
-                <view name="success">/templates/admin/helper-upload-data.vm</view>
-                <view name="securitybreach">/secure/views/securitybreach.jsp</view>
-            </action>
-        </actions>
-    </webwork1>
-    
-    <web-item name="JIRA QA Helper Admin Link" key="jira-security-example-verify-identity" system="true" weight="49" section="system.admin/globalsettings">
-        <label key="jiraqahelper.admin.adminlink" />
-        <link linkId="jiraqahelper_data">/secure/admin/UploadDataAction!default.jspa</link>
-    </web-item>
+        <resource type="i18n" name="jiraqahelper-i18n" location="i18n.JIRAQAHelper"/>	
+    
 </atlassian-plugin>
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.