package org.apache.maven.plugins.jira; /* * 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. */ 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; 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; /** * 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. * * @throws Exception on error */ public void doExecute() throws Exception { 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 { // CHECKSTYLE_OFF: LineLength // 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(); String url = new UrlBuilder( jiraUrl, "sr/jira.issueviews:searchrequest-xml/temp/SearchRequest.xml" ).addParameter( "tempMax", nbEntriesMax ).addParameter( "reset", "true" ).addParameter( "jqlQuery", jqlQuery ).build(); // CHECKSTYLE_ON: LineLength return url; } } 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(); } // CHECKSTYLE_OFF: LineLength String createdFilter = new ParameterQueryBuilder( log ).fixVersionIds( fixVersionIds ).statusIds( statusIds ).priorityIds( priorityIds ).resolutionIds( resolutionIds ).components( component ).typeIds( typeIds ).sortColumnNames( sortColumnNames ).filter( filter ).build(); // CHECKSTYLE_ON: LineLength 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(); } } }