| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.maven.plugins.jira; |
| |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.net.URLEncoder; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.commons.httpclient.Credentials; |
| import org.apache.commons.httpclient.Header; |
| import org.apache.commons.httpclient.HostConfiguration; |
| import org.apache.commons.httpclient.HttpClient; |
| import org.apache.commons.httpclient.HttpException; |
| import org.apache.commons.httpclient.HttpState; |
| import org.apache.commons.httpclient.HttpStatus; |
| import org.apache.commons.httpclient.StatusLine; |
| import org.apache.commons.httpclient.UsernamePasswordCredentials; |
| import org.apache.commons.httpclient.auth.AuthScope; |
| import org.apache.commons.httpclient.cookie.CookiePolicy; |
| import org.apache.commons.httpclient.methods.GetMethod; |
| import org.apache.commons.httpclient.params.HttpClientParams; |
| import org.apache.maven.plugin.MojoExecutionException; |
| import org.apache.maven.plugins.issues.Issue; |
| import org.codehaus.plexus.util.IOUtil; |
| import org.codehaus.plexus.util.StringUtils; |
| |
| /** |
| * Gets relevant issues for a JIRA report via HTTP/RSS. |
| * |
| * @author mfranken@xebia.com |
| * @author jruiz@exist.com |
| * @version $Id$ |
| */ |
| public final class ClassicJiraDownloader extends AbstractJiraDownloader { |
| public ClassicJiraDownloader() {} |
| |
| /** |
| * Execute the query on the JIRA server. |
| * |
| */ |
| public void doExecute() { |
| try { |
| HttpClient client = new HttpClient(); |
| |
| // MCHANGES-89 Allow circular redirects |
| HttpClientParams clientParams = client.getParams(); |
| clientParams.setBooleanParameter(HttpClientParams.ALLOW_CIRCULAR_REDIRECTS, true); |
| clientParams.setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY); // MCHANGES-237 |
| |
| HttpState state = new HttpState(); |
| |
| HostConfiguration hc = new HostConfiguration(); |
| |
| client.setHostConfiguration(hc); |
| |
| client.setState(state); |
| |
| String baseUrl = JiraHelper.getBaseUrl(project.getIssueManagement().getUrl()); |
| |
| getLog().debug("JIRA lives at: " + baseUrl); |
| // Here we only need the host part of the URL |
| determineProxy(baseUrl, client); |
| |
| prepareBasicAuthentication(client); |
| |
| boolean jiraAuthenticationSuccessful = false; |
| if (isJiraAuthenticationConfigured()) { |
| // Here we only need the parts up to and including the host part of the URL |
| jiraAuthenticationSuccessful = doJiraAuthentication(client, baseUrl); |
| } |
| |
| if ((isJiraAuthenticationConfigured() && jiraAuthenticationSuccessful) |
| || !isJiraAuthenticationConfigured()) { |
| String fullUrl; |
| |
| if (useJql) { |
| fullUrl = getJqlQueryURL(); |
| } else { |
| fullUrl = getParameterBasedQueryURL(client); |
| } |
| if (log.isDebugEnabled()) { |
| log.debug("download jira issues from url " + fullUrl); |
| } |
| |
| // execute the GET |
| download(client, fullUrl); |
| } |
| } catch (Exception e) { |
| if (project.getIssueManagement() != null) { |
| getLog().error("Error accessing " + project.getIssueManagement().getUrl(), e); |
| } else { |
| getLog().error("Error accessing mock project issues", e); |
| } |
| } |
| } |
| |
| private String getJqlQueryURL() { |
| // JQL is based on project names instead of project ID's |
| Map<String, String> urlMap = |
| JiraHelper.getJiraUrlAndProjectName(project.getIssueManagement().getUrl()); |
| String jiraUrl = urlMap.get("url"); |
| String jiraProject = urlMap.get("project"); |
| |
| if (jiraProject == null) { |
| throw new RuntimeException("The issue management URL in the POM does not include a JIRA project name"); |
| } else { |
| // create the URL for getting the proper issues from JIRA |
| String jqlQuery = new JqlQueryBuilder(log) |
| .project(jiraProject) |
| .fixVersion(getFixFor()) |
| .fixVersionIds(fixVersionIds) |
| .statusIds(statusIds) |
| .priorityIds(priorityIds) |
| .resolutionIds(resolutionIds) |
| .components(component) |
| .typeIds(typeIds) |
| .sortColumnNames(sortColumnNames) |
| .build(); |
| |
| return new UrlBuilder(jiraUrl, "sr/jira.issueviews:searchrequest-xml/temp/SearchRequest.xml") |
| .addParameter("tempMax", nbEntriesMax) |
| .addParameter("reset", "true") |
| .addParameter("jqlQuery", jqlQuery) |
| .build(); |
| } |
| } |
| |
| private String getParameterBasedQueryURL(HttpClient client) { |
| Map<String, String> urlMap = |
| JiraHelper.getJiraUrlAndProjectId(project.getIssueManagement().getUrl()); |
| String jiraUrl = urlMap.get("url"); |
| String jiraId = urlMap.get("id"); |
| |
| if (jiraId == null || jiraId.length() == 0) { |
| log.debug("The JIRA URL " + project.getIssueManagement().getUrl() |
| + " doesn't include a pid, trying to extract it from JIRA."); |
| jiraId = JiraHelper.getPidFromJira(log, project.getIssueManagement().getUrl(), client); |
| } |
| |
| if (jiraId == null) { |
| throw new RuntimeException("The issue management URL in the POM does not include a pid," |
| + " and it was not possible to extract it from the page at that URL."); |
| } else { |
| // create the URL for getting the proper issues from JIRA |
| String fullURL = jiraUrl + "/secure/IssueNavigator.jspa?view=rss&pid=" + jiraId; |
| |
| if (getFixFor() != null) { |
| fullURL += "&fixfor=" + getFixFor(); |
| } |
| |
| String createdFilter = new ParameterQueryBuilder(log) |
| .fixVersionIds(fixVersionIds) |
| .statusIds(statusIds) |
| .priorityIds(priorityIds) |
| .resolutionIds(resolutionIds) |
| .components(component) |
| .typeIds(typeIds) |
| .sortColumnNames(sortColumnNames) |
| .filter(filter) |
| .build(); |
| |
| if (createdFilter.charAt(0) != '&') { |
| fullURL += "&"; |
| } |
| fullURL += createdFilter; |
| |
| fullURL += ("&tempMax=" + nbEntriesMax + "&reset=true&decorator=none"); |
| |
| return fullURL; |
| } |
| } |
| |
| /** |
| * Check and prepare for basic authentication. |
| * |
| * @param client The client to prepare |
| */ |
| private void prepareBasicAuthentication(HttpClient client) { |
| if ((webUser != null) && (webUser.length() > 0)) { |
| client.getParams().setAuthenticationPreemptive(true); |
| |
| Credentials defaultcreds = new UsernamePasswordCredentials(webUser, webPassword); |
| |
| getLog().debug("Using username: " + webUser + " for Basic Authentication."); |
| |
| client.getState() |
| .setCredentials(new AuthScope(null, AuthScope.ANY_PORT, null, AuthScope.ANY_SCHEME), defaultcreds); |
| } |
| } |
| |
| /** |
| * Authenticate against JIRA. This method relies on jiraUser and jiraPassword being set. You can check this by |
| * calling isJiraAuthenticationConfigured(). |
| * |
| * @param client the HttpClient |
| * @param jiraUrl the JIRA installation |
| * @return <code>true</code> if the authentication was successful, otherwise <code>false</code> |
| */ |
| private boolean doJiraAuthentication(HttpClient client, final String jiraUrl) { |
| // log into JIRA if we have to |
| String loginUrl; |
| |
| StringBuilder loginLink = new StringBuilder(jiraUrl); |
| |
| loginLink.append("/login.jsp?os_destination=/secure/"); |
| |
| try { |
| loginLink.append("&os_username=").append(URLEncoder.encode(jiraUser, UTF_8)); |
| |
| String password = null; |
| if (jiraPassword != null) { |
| password = StringUtils.repeat("*", jiraPassword.length()); |
| } |
| getLog().debug("Login URL: " + loginLink + "&os_password=" + password); |
| |
| loginLink.append("&os_password=").append(URLEncoder.encode(jiraPassword, UTF_8)); |
| |
| loginUrl = loginLink.toString(); |
| |
| // execute the login |
| GetMethod loginGet = new GetMethod(loginUrl); |
| |
| client.executeMethod(loginGet); |
| |
| if (loginSucceeded(loginGet)) { |
| getLog().debug("Successfully logged in into JIRA."); |
| return true; |
| } else { |
| getLog().warn("Was unable to login into JIRA: wrong username and/or password."); |
| } |
| } catch (Exception e) { |
| if (getLog().isDebugEnabled()) { |
| getLog().error("Error trying to login into JIRA.", e); |
| } else { |
| getLog().error("Error trying to login into JIRA. Cause is: " + e.getLocalizedMessage()); |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Evaluate if the login attempt to JIRA was successful or not. We can't use the status code because JIRA returns |
| * 200 even if the login fails. |
| * |
| * @param loginGet The method that was executed |
| * @return <code>false</code> if we find an error message in the response body, otherwise <code>true</code> |
| * @todo There must be a nicer way to know whether we were able to login or not |
| */ |
| private boolean loginSucceeded(GetMethod loginGet) throws IOException { |
| final String loginFailureResponse = "your username and password are incorrect"; |
| |
| return !loginGet.getResponseBodyAsString().contains(loginFailureResponse); |
| } |
| |
| /** |
| * Setup proxy access if we have to. |
| * |
| * @param client the HttpClient |
| */ |
| private void determineProxy(String jiraUrl, HttpClient client) { |
| // see whether there is any proxy defined in maven |
| |
| getProxyInfo(jiraUrl); |
| |
| if (proxyHost != null) { |
| client.getHostConfiguration().setProxy(proxyHost, proxyPort); |
| |
| getLog().debug("Using proxy: " + proxyHost + " at port " + proxyPort); |
| |
| if (proxyUser != null) { |
| getLog().debug("Using proxy user: " + proxyUser); |
| |
| client.getState() |
| .setProxyCredentials( |
| new AuthScope(null, AuthScope.ANY_PORT, null, AuthScope.ANY_SCHEME), |
| new UsernamePasswordCredentials(proxyUser, proxyPass)); |
| } |
| } |
| } |
| |
| /** |
| * Downloads the given link using the configured HttpClient, possibly following redirects. |
| * |
| * @param cl the HttpClient |
| * @param link the URL to JIRA |
| */ |
| private void download(final HttpClient cl, final String link) { |
| InputStream in = null; |
| OutputStream out = null; |
| try { |
| GetMethod gm = new GetMethod(link); |
| |
| getLog().info("Downloading from JIRA at: " + link); |
| |
| gm.setFollowRedirects(true); |
| |
| cl.executeMethod(gm); |
| |
| StatusLine sl = gm.getStatusLine(); |
| |
| if (sl == null) { |
| getLog().error("Unknown error validating link: " + link); |
| |
| return; |
| } |
| |
| // if we get a redirect, do so |
| if (gm.getStatusCode() == HttpStatus.SC_MOVED_TEMPORARILY) { |
| Header locationHeader = gm.getResponseHeader("Location"); |
| |
| if (locationHeader == null) { |
| getLog().warn("Site sent redirect, but did not set Location header"); |
| } else { |
| String newLink = locationHeader.getValue(); |
| |
| getLog().debug("Following redirect to " + newLink); |
| |
| download(cl, newLink); |
| } |
| } |
| |
| if (gm.getStatusCode() == HttpStatus.SC_OK) { |
| in = gm.getResponseBodyAsStream(); |
| |
| if (!output.getParentFile().exists()) { |
| output.getParentFile().mkdirs(); |
| } |
| |
| // write the response to file |
| out = new FileOutputStream(output); |
| IOUtil.copy(in, out); |
| out.close(); |
| out = null; |
| in.close(); |
| in = null; |
| |
| getLog().debug("Downloading from JIRA was successful"); |
| } else { |
| getLog().warn("Downloading from JIRA failed. Received: [" + gm.getStatusCode() + "]"); |
| } |
| } catch (HttpException e) { |
| if (getLog().isDebugEnabled()) { |
| getLog().error("Error downloading issues from JIRA:", e); |
| } else { |
| getLog().error("Error downloading issues from JIRA url: " + e.getLocalizedMessage()); |
| } |
| } catch (IOException e) { |
| if (getLog().isDebugEnabled()) { |
| getLog().error("Error downloading issues from JIRA:", e); |
| } else { |
| getLog().error("Error downloading issues from JIRA. Cause is " + e.getLocalizedMessage()); |
| } |
| } finally { |
| IOUtil.close(out); |
| IOUtil.close(in); |
| } |
| } |
| |
| public List<Issue> getIssueList() throws MojoExecutionException { |
| if (output.isFile()) { |
| JiraXML jira = new JiraXML(log, jiraDatePattern); |
| jira.parseXML(output); |
| getLog().info("The JIRA version is '" + jira.getJiraVersion() + "'"); |
| return jira.getIssueList(); |
| } else { |
| getLog().warn("JIRA file " + output.getPath() + " doesn't exist."); |
| return Collections.emptyList(); |
| } |
| } |
| } |