View Javadoc

1   package org.apache.maven.jira;
2   
3   /* ====================================================================
4    *   Copyright 2001-2004 The Apache Software Foundation.
5    *
6    *   Licensed under the Apache License, Version 2.0 (the "License");
7    *   you may not use this file except in compliance with the License.
8    *   You may obtain a copy of the License at
9    *
10   *       http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *   Unless required by applicable law or agreed to in writing, software
13   *   distributed under the License is distributed on an "AS IS" BASIS,
14   *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   *   See the License for the specific language governing permissions and
16   *   limitations under the License.
17   * ====================================================================
18   */
19  
20  import java.io.File;
21  import java.io.FileWriter;
22  import java.io.IOException;
23  import java.io.PrintWriter;
24  
25  import java.text.NumberFormat;
26  import java.text.ParsePosition;
27  
28  import java.util.HashMap;
29  import java.util.Map;
30  
31  import org.apache.commons.httpclient.Credentials;
32  import org.apache.commons.httpclient.Header;
33  import org.apache.commons.httpclient.HostConfiguration;
34  import org.apache.commons.httpclient.HttpClient;
35  import org.apache.commons.httpclient.HttpException;
36  import org.apache.commons.httpclient.HttpState;
37  import org.apache.commons.httpclient.HttpStatus;
38  import org.apache.commons.httpclient.NTCredentials;
39  import org.apache.commons.httpclient.StatusLine;
40  import org.apache.commons.httpclient.UsernamePasswordCredentials;
41  import org.apache.commons.httpclient.methods.GetMethod;
42  import org.apache.commons.logging.Log;
43  import org.apache.commons.logging.LogFactory;
44  import org.apache.maven.jelly.MavenJellyContext;
45  import org.apache.maven.project.Project;
46  
47  /**
48   * Gets relevant issues in RSS from a given JIRA installation.
49   *
50   * Based on version 1.1.2 and patch by Dr. Spock (MPJIRA-8)
51   *
52   * @author mfranken@xebia.com
53   */
54  public final class JiraDownloader
55  {
56      /**
57       * Log for debug output.
58       */
59      private static final Log LOG = LogFactory.getLog( JiraDownloader.class );
60  
61      /** Mapping containing all JIRA status values. */
62      private static Map statusMap = new HashMap();
63  
64      /** Mapping containing all JIRA resolution values. */
65      private static Map resolutionMap = new HashMap();
66  
67      /** Mapping containing all JIRA priority values. */
68      private static Map priorityMap = new HashMap();
69  
70      static
71      {
72          statusMap.put( "Open", "1" );
73          statusMap.put( "In Progress", "3" );
74          statusMap.put( "Reopened", "4" );
75          statusMap.put( "Resolved", "5" );
76          statusMap.put( "Closed", "6" );
77  
78          resolutionMap.put( "Unresolved", "-1" );
79          resolutionMap.put( "Fixed", "1" );
80          resolutionMap.put( "Won't Fix", "2" );
81          resolutionMap.put( "Duplicate", "3" );
82          resolutionMap.put( "Incomplete", "4" );
83          resolutionMap.put( "Cannot Reproduce", "5" );
84  
85          priorityMap.put( "Blocker", "1" );
86          priorityMap.put( "Critical", "2" );
87          priorityMap.put( "Major", "3" );
88          priorityMap.put( "Minor", "4" );
89          priorityMap.put( "Trivial", "5" );
90      }
91  
92      /** Output file for xml document. */
93      private File output;
94  
95      /** The maximum number of entries to show. */
96      private int nbEntriesMax;
97  
98      /** The filter to apply to query to JIRA. */
99      private String filter;
100 
101     /** Ids of status to show, as comma separated string. */
102     private String status;
103 
104     /** Ids of resolution to show, as comma separated string. */
105     private String resolution;
106 
107     /** Ids of priority to show, as comma separated string. */
108     private String priority;
109 
110     /** The component to show. */
111     private String component;
112 
113     /** The username to LOG into JIRA. */
114     private String jiraUser;
115 
116     /** The password to LOG into JIRA. */
117     private String jiraPassword;
118 
119     /** The username to LOG into webserver. */
120     private String webUser;
121 
122     /** The password to LOG into webserver. */
123     private String webPassword;
124 
125     /** The host to use if you are using NTLM authentication. */
126     private String proxyNtlmHost;
127 
128     /**  The NT domain to use if you are using NTLM authentication. */
129     private String proxyNtlmDomain;
130 
131     /** The maven project. */
132     private Project project;
133 
134     /** Include a Jira roadmap. */
135     private boolean roadmap;
136 
137     /**
138      * Creates a filter given the maven.jira parameters and some defaults.
139      *
140      * @return request parameters to be added to URL used for downloading the JIRA issues
141      */
142     private String createFilter()
143     {
144         if ( ( this.filter != null ) && ( this.filter.length() > 0 ) )
145         {
146             if ( this.filter.charAt( 0 ) == '&' )
147             {
148                 return this.filter.substring( 1 );
149             }
150 
151             return this.filter;
152         }
153 
154         StringBuffer localFilter = new StringBuffer();
155 
156         // get the Status Ids
157         if ( status != null )
158         {
159             String[] stats = status.split( "," );
160 
161             for ( int i = 0; i < stats.length; i++ )
162             {
163                 String statusParam = (String) statusMap.get( stats[i] );
164 
165                 if ( statusParam != null )
166                 {
167                     localFilter.append( "&status=" + statusParam );
168                 }
169             }
170         }
171 
172         // get the Priority Ids
173         if ( priority != null )
174         {
175             String[] prios = priority.split( "," );
176 
177             for ( int i = 0; i < prios.length; i++ )
178             {
179                 String priorityParam = (String) priorityMap.get( prios[i] );
180 
181                 if ( priorityParam != null )
182                 {
183                     localFilter.append( "&priority=" + priorityParam );
184                 }
185             }
186         }
187 
188         if ( resolution != null )
189         {
190             // get the Resolution Ids
191             String[] resos = resolution.split( "," );
192 
193             for ( int i = 0; i < resos.length; i++ )
194             {
195                 String resoParam = (String) resolutionMap.get( resos[i] );
196 
197                 if ( resoParam != null )
198                 {
199                     localFilter.append( "&resolution=" + resoParam );
200                 }
201             }
202         }
203 
204         // add all components
205         if ( component != null )
206         {
207             String[] components = component.split( "," );
208 
209             for ( int i = 0; i < components.length; i++ )
210             {
211                 if ( components[i].length() > 0 )
212                 {
213                     localFilter.append( "&component=" + components[i] );
214                 }
215             }
216         }
217 
218         // add default sorting (by priority and then creation date)
219         String sort =
220             "&sorter/field=created&sorter/order=DESC"
221             + "&sorter/field=priority&sorter/order=DESC";
222 
223         return localFilter + sort;
224     }
225 
226     /**
227      * Execute the query on the JIRA server.
228      *
229      * @throws Exception
230      *             on error
231      */
232     public void doExecute()
233         throws Exception
234     {
235         if ( project == null )
236         {
237             throw new Exception( "No project set." );
238         }
239 
240         if ( project.getIssueTrackingUrl() == null )
241         {
242             throw new Exception( "No issue tracking url set." );
243         }
244 
245         try
246         {
247             HttpClient cl = new HttpClient();
248             HttpState state = new HttpState();
249             HostConfiguration hc = new HostConfiguration();
250 
251             cl.setHostConfiguration( hc );
252             cl.setState( state );
253 
254             determineProxy( cl );
255 
256             // get the Jira URL and project id
257             String url = project.getIssueTrackingUrl();
258 
259             // chop off the parameter part
260             int pos = url.indexOf( "?" );
261 
262             // and get the id while we're at it
263             String id = null;
264 
265             if ( pos >= 0 )
266             {
267                 // url
268                 id = url.substring( url.lastIndexOf( "=" ) + 1 );
269             }
270 
271             String jiraUrl = url.substring( 0, url.lastIndexOf( "/" ) );
272 
273             if ( jiraUrl.endsWith( "secure" ) || jiraUrl.endsWith( "browse" ) )
274             {
275                 jiraUrl = jiraUrl.substring( 0, jiraUrl.lastIndexOf( "/" ) );
276             }
277 
278             LOG.info( "Jira lives at: " + jiraUrl );
279             doAuthentication( cl, jiraUrl );
280 
281             String projectPage = "";
282 
283             if ( ( id == null ) || roadmap )
284             {
285                 GetMethod gm =
286                     new GetMethod( url
287                         + "?report=com.atlassian.jira.plugin.system.project:roadmap-panel" );
288 
289                 try
290                 {
291                     cl.executeMethod( gm );
292                     LOG.info( "Succesfully reached JIRA." );
293                 }
294                 catch ( Exception e )
295                 {
296                     if ( LOG.isDebugEnabled() )
297                     {
298                         LOG.error( "Unable to reach JIRA project page:", e );
299                     }
300                     else
301                     {
302                         LOG.error(
303                             "Unable to reach JIRA project page. Cause is: "
304                             + e.getLocalizedMessage() );
305                     }
306                 }
307 
308                 projectPage = gm.getResponseBodyAsString();
309             }
310 
311             if ( id == null )
312             {
313                 LOG.info( "Jira URL " + url
314                     + " doesn't include a pid, trying to get it" );
315 
316                 int pidIndex = projectPage.indexOf( "pid=" ); // @todo, a safer way to get the PID
317 
318                 if ( pidIndex == -1 )
319                 {
320                     // fail
321                     LOG.error( "Unable to get JIRA pid using url "
322                         + project.getIssueTrackingUrl() );
323 
324                     return;
325                 }
326 
327                 NumberFormat nf = NumberFormat.getInstance();
328                 Number pidNumber =
329                     nf.parse( projectPage, new ParsePosition( pidIndex + 4 ) );
330 
331                 id = Integer.toString( pidNumber.intValue() );
332             }
333 
334             // create the URL for getting the proper iussues from JIRA
335             String fullURL =
336                 jiraUrl + "/secure/IssueNavigator.jspa?view=rss&pid=" + id;
337 
338             fullURL += createFilter();
339             fullURL += ( "&tempMax=" + nbEntriesMax
340             + "&reset=true&decorator=none" );
341 
342             // execute the GET
343             download( cl, fullURL, output );
344 
345             if ( roadmap )
346             {
347                 int fixforIndex = projectPage.indexOf( "fixfor=" ); // @todo, a safer way to get the PID
348 
349                 if ( fixforIndex == -1 )
350                 {
351                     // fail
352                     LOG.error( "Unable to get JIRA roadmap using url "
353                         + project.getIssueTrackingUrl() );
354 
355                     return;
356                 }
357 
358                 NumberFormat nf = NumberFormat.getInstance();
359                 Number fixforNumber =
360                     nf.parse( projectPage, new ParsePosition( fixforIndex + 7 ) );
361                 String fixfor = Integer.toString( fixforNumber.intValue() );
362 
363                 // if fixfor = "-1": no versions scheduled in roadmap
364                 if ( !fixfor.equals( "-1" ) )
365                 {
366                     setFilter( "&&fixfor=" + fixfor
367                         + "&sorter/field=status&sorter/order=ASC" );
368                     fullURL =
369                         jiraUrl + "/secure/IssueNavigator.jspa?view=rss&pid=" + id;
370                     fullURL += createFilter();
371                     fullURL += ( "&tempMax=" + nbEntriesMax
372                     + "&reset=true&decorator=none" );
373 
374                     String outFile = output.getAbsolutePath().replace('\\','/');
375                     int endIndex = outFile.lastIndexOf( '/' );
376 
377                     outFile =
378                         outFile.substring( 0, endIndex ) + "/jira-roadmap.xml";
379 
380                     // execute the GET
381                     download( cl, fullURL, new File( outFile ) );
382                 }
383             }
384         }
385         catch ( Exception e )
386         {
387             LOG.error( "Error accessing " + project.getIssueTrackingUrl(), e );
388         }
389     }
390 
391     /**
392      * Authenticate against webserver and into JIRA if we have to.
393      *
394      * @param client
395      *            the HttpClient
396      * @param jiraUrl
397      *            the JIRA installation
398      */
399     private void doAuthentication( HttpClient client, final String jiraUrl )
400     {
401         // check and prepare for basic authentication
402         if ( ( webUser != null ) && ( webUser.length() > 0 ) )
403         {
404             client.getState().setAuthenticationPreemptive( true );
405 
406             Credentials defaultcreds;
407             if ( this.proxyNtlmHost != null && this.proxyNtlmDomain != null)
408             {
409                 defaultcreds =
410                     new NTCredentials( webUser, webPassword,
411                                         proxyNtlmHost, proxyNtlmDomain );
412             }
413             else
414             {
415                 defaultcreds =
416                     new UsernamePasswordCredentials( webUser, webPassword );
417             }
418 
419             LOG.info( "Using username: " + webUser
420                 + " for Basic Authentication against the webserver at "
421                 + jiraUrl );
422             client.getState().setCredentials( null, null, defaultcreds );
423         }
424 
425         // LOG into JIRA if we have to
426         String loginUrl = null;
427 
428         if ( ( jiraUser != null ) && ( jiraUser.length() > 0 )
429             && ( jiraPassword != null ) )
430         {
431             StringBuffer loginLink = new StringBuffer( jiraUrl );
432 
433             loginLink.append( "/login.jsp?os_destination=/secure/" );
434             loginLink.append( "&os_username=" ).append( jiraUser );
435             LOG.info( "Login URL: " + loginLink + "&os_password=*******" );
436             loginLink.append( "&os_password=" ).append( jiraPassword );
437             loginUrl = loginLink.toString();
438         }
439 
440         // execute the login
441         if ( loginUrl != null )
442         {
443             GetMethod loginGet = new GetMethod( loginUrl );
444 
445             try
446             {
447                 client.executeMethod( loginGet );
448                 LOG.info( "Succesfully logged in into JIRA." );
449             }
450             catch ( Exception e )
451             {
452                 if ( LOG.isDebugEnabled() )
453                 {
454                     LOG.error( "Error trying to login into JIRA:", e );
455                 }
456                 else
457                 {
458                     LOG.error( "Error trying to login into JIRA. Cause is: "
459                         + e.getLocalizedMessage() );
460                 }
461 
462                 // continue any way, probably will fail later if authentication was necesaaray afterall
463             }
464         }
465     }
466 
467     /**
468      * Setup proxy access if we have to.
469      *
470      * @param client
471      *            the HttpClient
472      */
473     private void determineProxy( HttpClient client )
474     {
475         // see whether there is any proxy defined in maven
476         if ( project == null )
477         {
478             LOG.error( "No project set. No proxy info available." );
479 
480             return;
481         }
482 
483         MavenJellyContext ctx = project.getContext();
484 
485         if ( ctx == null )
486         {
487             LOG.error( "Maven project has no context. No proxy info available." );
488 
489             return;
490         }
491 
492         String proxyHost = ctx.getProxyHost();
493 
494         if ( proxyHost != null )
495         {
496             String proxyPort = ctx.getProxyPort();
497 
498             client.getHostConfiguration().setProxy( proxyHost,
499                 Integer.parseInt( proxyPort ) );
500             LOG.info( "Using proxy: " + proxyHost + " at port " + proxyPort );
501 
502             String proxyUser = ctx.getProxyUserName();
503 
504             if ( proxyUser != null )
505             {
506                 LOG.info( "Using proxy user: " + proxyUser );
507 
508                 String proxyPass = ctx.getProxyPassword();
509 
510                 client.getState().setProxyCredentials( null, null,
511                     new UsernamePasswordCredentials( proxyUser, proxyPass ) );
512             }
513         }
514     }
515 
516     /**
517      * Downloads the given link using the configured HttpClient, possibly following redirects.
518      *
519      * @param cl
520      *            the HttpClient
521      * @param link
522      *            the JiraUrl
523      * @param outFile
524      *            the output file
525      * @return
526      */
527     private void download( final HttpClient cl, final String link,
528         final File outFile )
529     {
530         try
531         {
532             GetMethod gm = new GetMethod( link );
533 
534             LOG.info( "Downloading " + link );
535             gm.setFollowRedirects( true );
536             cl.executeMethod( gm );
537 
538             final String strGetResponseBody = gm.getResponseBodyAsString();
539 
540             // write the reponse to file
541             PrintWriter pw = new PrintWriter( new FileWriter( outFile ) );
542 
543             pw.print( strGetResponseBody );
544             pw.close();
545 
546             StatusLine sl = gm.getStatusLine();
547 
548             if ( sl == null )
549             {
550                 LOG.info( "Unknown error validating link : " + link );
551 
552                 return;
553             }
554 
555             // if we get a redirect, do so
556             if ( gm.getStatusCode() == HttpStatus.SC_MOVED_TEMPORARILY )
557             {
558                 Header locationHeader = gm.getResponseHeader( "Location" );
559 
560                 if ( locationHeader == null )
561                 {
562                     LOG.info( "Site sent redirect, but did not set Location header" );
563                 }
564                 else
565                 {
566                     String newLink = locationHeader.getValue();
567 
568                     LOG.debug( "Following redirect to " + newLink );
569                     download( cl, newLink, outFile );
570                 }
571             }
572 
573             if ( gm.getStatusCode() != HttpStatus.SC_OK )
574             {
575                 LOG.warn( "Received: [" + gm.getStatusCode() + "]" );
576             }
577         }
578         catch ( HttpException e )
579         {
580             if ( LOG.isDebugEnabled() )
581             {
582                 LOG.error( "Error downloading issues from JIRA:", e );
583             }
584             else
585             {
586                 LOG.error( "Error downloading issues from JIRA. Cause is: "
587                     + e.getLocalizedMessage() );
588             }
589         }
590         catch ( IOException e )
591         {
592             if ( LOG.isDebugEnabled() )
593             {
594                 LOG.error( "Error downloading issues from JIRA:", e );
595             }
596             else
597             {
598                 LOG.error( "Error downloading issues from JIRA. Cause is: "
599                     + e.getLocalizedMessage() );
600             }
601         }
602     }
603 
604     /**
605      * Set the output file for the LOG.
606      *
607      * @param thisOutput
608      *            the output file
609      */
610     public void setOutput( final File thisOutput )
611     {
612         this.output = thisOutput;
613     }
614 
615     /**
616      * Sets the project.
617      *
618      * @param thisProject
619      *            The project to set
620      */
621     public void setProject( final Object thisProject )
622     {
623         this.project = (Project) thisProject;
624     }
625 
626     /**
627      * Sets the maximum number of Issues to show.
628      *
629      * @param nbEntries
630      *            The maximum number of Issues
631      */
632     public void setNbEntries( final int nbEntries )
633     {
634         nbEntriesMax = nbEntries;
635     }
636 
637     /**
638      * Sets the status.
639      *
640      * @param thisStatus
641      *            The id(s) of the status to show, as comma separated string
642      */
643     public void setStatus( final String thisStatus )
644     {
645         status = thisStatus;
646     }
647 
648     /**
649      * Sets the priority.
650      *
651      * @param thisPriority
652      *            The id(s) of the priority to show, as comma separated string
653      */
654     public void setPriority( final String thisPriority )
655     {
656         priority = thisPriority;
657     }
658 
659     /**
660      * Sets the resolution.
661      *
662      * @param thisResolution
663      *            The id(s) of the resolution to show, as comma separated string
664      */
665     public void setResolution( final String thisResolution )
666     {
667         resolution = thisResolution;
668     }
669 
670     /**
671      * Sets the password for authentication against the webserver.
672      *
673      * @param thisWebPassword
674      *            The password of the webserver
675      */
676     public void setWebPassword( final String thisWebPassword )
677     {
678         this.webPassword = thisWebPassword;
679     }
680 
681     /**
682      * Sets the username for authentication against the webserver.
683      *
684      * @param thisWebUser
685      *            The username of the webserver
686      */
687     public void setWebUser( final String thisWebUser )
688     {
689         this.webUser = thisWebUser;
690     }
691 
692     /**
693      * Sets the NTLM host.
694      *
695      * @param thisProxyNtlmHost The NTLM host to set.
696      */
697     public void setProxyNtlmHost( final String thisProxyNtlmHost )
698     {
699         this.proxyNtlmHost = thisProxyNtlmHost;
700     }
701 
702     /**
703      * Sets the NTLM domain.
704      *
705      * @param thisProxyNtlmDomain The NTLM domain to set.
706      */
707     public void setProxyNtlmDomain( final String thisProxyNtlmDomain )
708     {
709         this.proxyNtlmDomain = thisProxyNtlmDomain;
710     }
711 
712     /**
713      * Sets the password to LOG into a secured JIRA.
714      *
715      * @param thisJiraPassword
716      *            The password for JIRA
717      */
718     public void setJiraPassword( final String thisJiraPassword )
719     {
720         this.jiraPassword = thisJiraPassword;
721     }
722 
723     /**
724      * Sets the username to LOG into a secured JIRA.
725      *
726      * @param thisJiraUser
727      *            The username for JIRA
728      */
729     public void setJiraUser( final String thisJiraUser )
730     {
731         this.jiraUser = thisJiraUser;
732     }
733 
734     /**
735      * Sets the filter to apply to query to JIRA.
736      *
737      * @param thisFilter
738      *            The filter to query JIRA
739      */
740     public void setFilter( final String thisFilter )
741     {
742         this.filter = thisFilter;
743     }
744 
745     /**
746      * Sets the component(s) to apply to query JIRA.
747      *
748      * @param theseComponents
749      *            The id(s) of components to show, as comma separated string
750      */
751     public void setComponent( final String theseComponents )
752     {
753         this.component = theseComponents;
754     }
755 
756     /**
757      * Sets the roadmap property.
758      * @param thisRoadmap The roadmap.
759      */
760     public void setRoadmap( final boolean thisRoadmap )
761     {
762         this.roadmap = thisRoadmap;
763     }
764 }