1 package org.apache.maven.plugin.jira;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.commons.httpclient.cookie.CookiePolicy;
23 import org.apache.commons.httpclient.Credentials;
24 import org.apache.commons.httpclient.Header;
25 import org.apache.commons.httpclient.HostConfiguration;
26 import org.apache.commons.httpclient.HttpClient;
27 import org.apache.commons.httpclient.HttpException;
28 import org.apache.commons.httpclient.HttpState;
29 import org.apache.commons.httpclient.HttpStatus;
30 import org.apache.commons.httpclient.StatusLine;
31 import org.apache.commons.httpclient.UsernamePasswordCredentials;
32 import org.apache.commons.httpclient.params.HttpClientParams;
33 import org.apache.commons.httpclient.auth.AuthScope;
34 import org.apache.commons.httpclient.methods.GetMethod;
35 import org.apache.maven.plugin.MojoExecutionException;
36 import org.apache.maven.plugin.issues.Issue;
37 import org.apache.maven.plugin.logging.Log;
38 import org.apache.maven.project.MavenProject;
39 import org.apache.maven.settings.Proxy;
40 import org.apache.maven.settings.Settings;
41 import org.apache.maven.wagon.proxy.ProxyInfo;
42 import org.codehaus.plexus.util.IOUtil;
43 import org.codehaus.plexus.util.StringUtils;
44
45 import java.io.File;
46 import java.io.FileOutputStream;
47 import java.io.IOException;
48 import java.io.InputStream;
49 import java.io.OutputStream;
50 import java.net.MalformedURLException;
51 import java.net.URL;
52 import java.net.URLEncoder;
53 import java.util.Collections;
54 import java.util.HashMap;
55 import java.util.List;
56 import java.util.Locale;
57 import java.util.Map;
58
59
60
61
62
63
64
65
66
67
68 public abstract class AbstractJiraDownloader
69 {
70 private static final String UTF_8 = "UTF-8";
71
72
73 protected Log log;
74
75 private File output;
76
77 private int nbEntriesMax;
78
79 private String filter;
80
81 private String fixVersionIds;
82
83 private String statusIds;
84
85 private String resolutionIds;
86
87 private String priorityIds;
88
89 private String component;
90
91 private String typeIds;
92
93 private String sortColumnNames;
94
95 private String jiraUser;
96
97 private String jiraPassword;
98
99 private String webUser;
100
101 private String webPassword;
102
103 private MavenProject project;
104
105 private Settings settings;
106
107 protected final Map<String,String> statusMap = new HashMap<String,String>( 8 );
108
109 protected final Map<String,String> resolutionMap = new HashMap<String,String>( 8 );
110
111 protected final Map<String,String> priorityMap = new HashMap<String,String>( 8 );
112
113 protected final Map<String,String> typeMap = new HashMap<String,String>( 8 );
114
115 protected String jiraDatePattern;
116
117
118
119
120
121
122 private String createFilter()
123 {
124
125 if ( ( this.filter != null ) && ( this.filter.length() > 0 ) )
126 {
127 return this.filter;
128 }
129
130 StringBuffer localFilter = new StringBuffer( 16 );
131
132
133 if ( fixVersionIds != null )
134 {
135 String[] fixVersions = fixVersionIds.split( "," );
136
137 for ( int i = 0; i < fixVersions.length; i++ )
138 {
139 if ( fixVersions[i].length() > 0 )
140 {
141 localFilter.append( "&fixfor=" ).append( fixVersions[i].trim() );
142 }
143 }
144 }
145
146
147 if ( statusIds != null )
148 {
149 String[] stats = statusIds.split( "," );
150 for ( String stat : stats )
151 {
152 stat = stat.trim();
153 String statusParam = statusMap.get( stat );
154
155 if ( statusParam != null )
156 {
157 localFilter.append( "&statusIds=" ).append( statusParam );
158 }
159 else
160 {
161
162 try
163 {
164 Integer.parseInt( stat );
165 localFilter.append( "&statusIds=" ).append( stat );
166 }
167 catch ( NumberFormatException nfe )
168 {
169 getLog().error( "maven-changes-plugin: invalid statusId " + stat );
170 }
171 }
172 }
173 }
174
175
176 if ( priorityIds != null )
177 {
178 String[] prios = priorityIds.split( "," );
179
180 for ( String prio : prios )
181 {
182 prio = prio.trim();
183 String priorityParam = priorityMap.get( prio );
184
185 if ( priorityParam != null )
186 {
187 localFilter.append( "&priorityIds=" ).append( priorityParam );
188 }
189 }
190 }
191
192
193 if ( resolutionIds != null )
194 {
195 String[] resos = resolutionIds.split( "," );
196
197 for ( String reso : resos )
198 {
199 reso = reso.trim();
200 String resoParam = resolutionMap.get( reso );
201
202 if ( resoParam != null )
203 {
204 localFilter.append( "&resolutionIds=" ).append( resoParam );
205 }
206 }
207 }
208
209
210 if ( component != null )
211 {
212 String[] components = component.split( "," );
213
214 for ( String component : components )
215 {
216 component = component.trim();
217 if ( component.length() > 0 )
218 {
219 localFilter.append( "&component=" ).append( component );
220 }
221 }
222 }
223
224
225 if ( typeIds != null )
226 {
227 String[] types = typeIds.split( "," );
228
229 for ( String type : types )
230 {
231 String typeParam = typeMap.get( type.trim() );
232
233 if ( typeParam != null )
234 {
235 localFilter.append( "&type=" ).append( typeParam );
236 }
237 }
238 }
239
240
241 int validSortColumnNames = 0;
242 if ( sortColumnNames != null )
243 {
244 String[] sortColumnNamesArray = sortColumnNames.split( "," );
245
246 for ( int i = sortColumnNamesArray.length - 1; i >= 0; i-- )
247 {
248 String lowerColumnName = sortColumnNamesArray[i].trim().toLowerCase( Locale.ENGLISH );
249 boolean descending = false;
250 String fieldName = null;
251 if ( lowerColumnName.endsWith( "desc" ) )
252 {
253 descending = true;
254 lowerColumnName = lowerColumnName.substring( 0, lowerColumnName.length() - 4 ).trim();
255 }
256 else if ( lowerColumnName.endsWith( "asc" ) )
257 {
258 descending = false;
259 lowerColumnName = lowerColumnName.substring( 0, lowerColumnName.length() - 3 ).trim();
260 }
261
262 if ( "key".equals( lowerColumnName ) )
263 {
264 fieldName = "issuekey";
265 }
266 else if ( "summary".equals( lowerColumnName ) )
267 {
268 fieldName = lowerColumnName;
269 }
270 else if ( "status".equals( lowerColumnName ) )
271 {
272 fieldName = lowerColumnName;
273 }
274 else if ( "resolution".equals( lowerColumnName ) )
275 {
276 fieldName = lowerColumnName;
277 }
278 else if ( "assignee".equals( lowerColumnName ) )
279 {
280 fieldName = lowerColumnName;
281 }
282 else if ( "reporter".equals( lowerColumnName ) )
283 {
284 fieldName = lowerColumnName;
285 }
286 else if ( "type".equals( lowerColumnName ) )
287 {
288 fieldName = "issuetype";
289 }
290 else if ( "priority".equals( lowerColumnName ) )
291 {
292 fieldName = lowerColumnName;
293 }
294 else if ( "version".equals( lowerColumnName ) )
295 {
296 fieldName = "versions";
297 }
298 else if ( "fix version".equals( lowerColumnName ) )
299 {
300 fieldName = "fixVersions";
301 }
302 else if ( "component".equals( lowerColumnName ) )
303 {
304 fieldName = "components";
305 }
306 else if ( "created".equals( lowerColumnName ) )
307 {
308 fieldName = lowerColumnName;
309 }
310 else if ( "updated".equals( lowerColumnName ) )
311 {
312 fieldName = lowerColumnName;
313 }
314 if ( fieldName != null )
315 {
316 localFilter.append( "&sorter/field=" );
317 localFilter.append( fieldName );
318 localFilter.append( "&sorter/order=" );
319 localFilter.append( descending ? "DESC" : "ASC" );
320 validSortColumnNames++;
321 }
322 else
323 {
324
325 getLog().error(
326 "maven-changes-plugin: The configured value '" + lowerColumnName
327 + "' for sortColumnNames is not correct." );
328 }
329 }
330 }
331 if ( validSortColumnNames == 0 )
332 {
333
334 getLog().error(
335 "maven-changes-plugin: None of the configured sortColumnNames '" + sortColumnNames + "' are correct." );
336 }
337
338
339 return localFilter.toString();
340 }
341
342
343
344
345
346
347 public void doExecute()
348 throws Exception
349 {
350 try
351 {
352 HttpClient client = new HttpClient();
353
354
355 HttpClientParams clientParams = client.getParams();
356 clientParams.setBooleanParameter( HttpClientParams.ALLOW_CIRCULAR_REDIRECTS, true );
357 clientParams.setCookiePolicy( CookiePolicy.BROWSER_COMPATIBILITY );
358
359 HttpState state = new HttpState();
360
361 HostConfiguration hc = new HostConfiguration();
362
363 client.setHostConfiguration( hc );
364
365 client.setState( state );
366
367 Map<String,String> urlMap = JiraHelper.getJiraUrlAndProjectId( project.getIssueManagement().getUrl() );
368
369 String jiraUrl = urlMap.get( "url" );
370 getLog().debug( "JIRA lives at: " + jiraUrl );
371
372 String jiraId = urlMap.get( "id" );
373
374 determineProxy( jiraUrl, client );
375
376 prepareBasicAuthentication( client );
377
378 boolean jiraAuthenticationSuccessful = false;
379 if ( isJiraAuthenticationConfigured() )
380 {
381 jiraAuthenticationSuccessful = doJiraAuthentication( client, jiraUrl );
382 }
383
384 if ( ( isJiraAuthenticationConfigured() && jiraAuthenticationSuccessful )
385 || !isJiraAuthenticationConfigured() )
386 {
387 if ( jiraId == null || jiraId.length() == 0 )
388 {
389 log.debug( "The JIRA URL " + project.getIssueManagement().getUrl()
390 + " doesn't include a pid, trying to extract it from JIRA." );
391 jiraId = JiraHelper.getPidFromJira( log, project.getIssueManagement().getUrl(), client );
392 }
393
394 if ( jiraId == null )
395 {
396 getLog().error( "The issue management URL in the POM does not include a pid,"
397 + " and it was not possible to extract it from the page at that URL." );
398 }
399 else
400 {
401
402 String fullURL = jiraUrl + "/secure/IssueNavigator.jspa?view=rss&pid=" + jiraId;
403
404 if ( getFixFor() != null )
405 {
406 fullURL += "&fixfor=" + getFixFor();
407 }
408
409 String createdFilter = createFilter();
410 if ( createdFilter.charAt( 0 ) != '&' )
411 {
412 fullURL += "&";
413 }
414 fullURL += createdFilter;
415
416 fullURL += ( "&tempMax=" + nbEntriesMax + "&reset=true&decorator=none" );
417
418 if ( log.isDebugEnabled() )
419 {
420 log.debug( "download jira issues from url " + fullURL );
421 }
422
423
424 download( client, fullURL );
425 }
426 }
427 }
428 catch ( Exception e )
429 {
430 if ( project.getIssueManagement() != null )
431 {
432 getLog().error( "Error accessing " + project.getIssueManagement().getUrl(), e );
433 }
434 else
435 {
436 getLog().error( "Error accessing mock project issues", e );
437 }
438 }
439 }
440
441
442
443
444
445
446 protected String getFixFor()
447 {
448 return null;
449 }
450
451
452
453
454
455
456 private void prepareBasicAuthentication( HttpClient client )
457 {
458 if ( ( webUser != null ) && ( webUser.length() > 0 ) )
459 {
460 client.getParams().setAuthenticationPreemptive( true );
461
462 Credentials defaultcreds = new UsernamePasswordCredentials( webUser, webPassword );
463
464 getLog().debug( "Using username: " + webUser + " for Basic Authentication." );
465
466 client.getState().setCredentials( new AuthScope( null, AuthScope.ANY_PORT, null, AuthScope.ANY_SCHEME ),
467 defaultcreds );
468 }
469 }
470
471
472
473
474
475
476
477
478
479
480 private boolean doJiraAuthentication( HttpClient client, final String jiraUrl )
481 {
482
483 String loginUrl = null;
484
485 StringBuffer loginLink = new StringBuffer( jiraUrl );
486
487 loginLink.append( "/login.jsp?os_destination=/secure/" );
488
489 try
490 {
491 loginLink.append( "&os_username=" ).append( URLEncoder.encode( jiraUser, UTF_8 ) );
492
493 String password = null;
494 if ( jiraPassword != null )
495 {
496 password = StringUtils.repeat( "*", jiraPassword.length() );
497 }
498 getLog().debug( "Login URL: " + loginLink + "&os_password=" + password );
499
500 loginLink.append( "&os_password=" ).append( URLEncoder.encode( jiraPassword, UTF_8 ) );
501
502 loginUrl = loginLink.toString();
503
504
505 GetMethod loginGet = new GetMethod( loginUrl );
506
507 client.executeMethod( loginGet );
508
509 if ( loginSucceeded( loginGet ) )
510 {
511 getLog().debug( "Successfully logged in into JIRA." );
512 return true;
513 }
514 else
515 {
516 getLog().warn( "Was unable to login into JIRA: wrong username and/or password." );
517 }
518 }
519 catch ( Exception e )
520 {
521 if ( getLog().isDebugEnabled() )
522 {
523 getLog().error( "Error trying to login into JIRA.", e );
524 }
525 else
526 {
527 getLog().error( "Error trying to login into JIRA. Cause is: " + e.getLocalizedMessage() );
528 }
529 }
530 return false;
531 }
532
533
534
535
536
537
538 private boolean isJiraAuthenticationConfigured()
539 {
540 return ( jiraUser != null ) && ( jiraUser.length() > 0 ) && ( jiraPassword != null );
541 }
542
543
544
545
546
547
548
549
550
551 private boolean loginSucceeded( GetMethod loginGet )
552 throws IOException
553 {
554 final String loginFailureResponse = "your username and password are incorrect";
555
556 return loginGet.getResponseBodyAsString().indexOf( loginFailureResponse ) == -1;
557 }
558
559
560
561
562
563
564 private void determineProxy( String jiraUrl, HttpClient client )
565 {
566
567 Proxy proxy = null;
568
569 String proxyHost = null;
570
571 int proxyPort = 0;
572
573 String proxyUser = null;
574
575 String proxyPass = null;
576
577 if ( project == null )
578 {
579 getLog().error( "No project set. No proxy info available." );
580
581 return;
582 }
583
584 if ( settings != null )
585 {
586 proxy = settings.getActiveProxy();
587 }
588
589 if ( proxy != null )
590 {
591
592 ProxyInfo proxyInfo = new ProxyInfo();
593 proxyInfo.setNonProxyHosts( proxy.getNonProxyHosts() );
594
595
596 URL url = null;
597 try
598 {
599 url = new URL( jiraUrl );
600 }
601 catch( MalformedURLException e )
602 {
603 getLog().error( "Invalid JIRA URL: " + jiraUrl + ". " + e.getMessage() );
604 }
605 String jiraHost = null;
606 if ( url != null )
607 {
608 jiraHost = url.getHost();
609 }
610
611
612
613
614
615 if ( JiraHelper.validateNonProxyHosts( proxyInfo, jiraHost ) )
616 {
617 return;
618 }
619
620 proxyHost = settings.getActiveProxy().getHost();
621
622 proxyPort = settings.getActiveProxy().getPort();
623
624 proxyUser = settings.getActiveProxy().getUsername();
625
626 proxyPass = settings.getActiveProxy().getPassword();
627
628 getLog().debug( proxyPass );
629 }
630
631 if ( proxyHost != null )
632 {
633 client.getHostConfiguration().setProxy( proxyHost, proxyPort );
634
635 getLog().debug( "Using proxy: " + proxyHost + " at port " + proxyPort );
636
637 if ( proxyUser != null )
638 {
639 getLog().debug( "Using proxy user: " + proxyUser );
640
641 client.getState().setProxyCredentials(
642 new AuthScope( null, AuthScope.ANY_PORT, null,
643 AuthScope.ANY_SCHEME ),
644 new UsernamePasswordCredentials( proxyUser, proxyPass ) );
645 }
646 }
647 }
648
649
650
651
652
653
654
655 private void download( final HttpClient cl, final String link )
656 {
657 try
658 {
659 GetMethod gm = new GetMethod( link );
660
661 getLog().info( "Downloading from JIRA at: " + link );
662
663 gm.setFollowRedirects( true );
664
665 cl.executeMethod( gm );
666
667 StatusLine sl = gm.getStatusLine();
668
669 if ( sl == null )
670 {
671 getLog().error( "Unknown error validating link: " + link );
672
673 return;
674 }
675
676
677 if ( gm.getStatusCode() == HttpStatus.SC_MOVED_TEMPORARILY )
678 {
679 Header locationHeader = gm.getResponseHeader( "Location" );
680
681 if ( locationHeader == null )
682 {
683 getLog().warn( "Site sent redirect, but did not set Location header" );
684 }
685 else
686 {
687 String newLink = locationHeader.getValue();
688
689 getLog().debug( "Following redirect to " + newLink );
690
691 download( cl, newLink );
692 }
693 }
694
695 if ( gm.getStatusCode() == HttpStatus.SC_OK )
696 {
697 final InputStream responseBodyStream = gm.getResponseBodyAsStream();
698
699 if ( !output.getParentFile().exists() )
700 {
701 output.getParentFile().mkdirs();
702 }
703
704
705 OutputStream out = null;
706 try
707 {
708 out = new FileOutputStream( output );
709 IOUtil.copy( responseBodyStream, out );
710 }
711 finally
712 {
713 IOUtil.close( out );
714 IOUtil.close( responseBodyStream );
715 }
716
717 getLog().debug( "Downloading from JIRA was successful" );
718 }
719 else
720 {
721 getLog().warn( "Downloading from JIRA failed. Received: [" + gm.getStatusCode() + "]" );
722 }
723 }
724 catch ( HttpException e )
725 {
726 if ( getLog().isDebugEnabled() )
727 {
728 getLog().error( "Error downloading issues from JIRA:", e );
729 }
730 else
731 {
732 getLog().error( "Error downloading issues from JIRA url: " + e.getLocalizedMessage() );
733
734 }
735 }
736 catch ( IOException e )
737 {
738 if ( getLog().isDebugEnabled() )
739 {
740 getLog().error( "Error downloading issues from JIRA:", e );
741 }
742 else
743 {
744 getLog().error( "Error downloading issues from JIRA. Cause is " + e.getLocalizedMessage() );
745 }
746 }
747 }
748
749 public List<Issue> getIssueList()
750 throws MojoExecutionException
751 {
752 if ( output.isFile() )
753 {
754 JiraXML jira = new JiraXML( log, jiraDatePattern );
755 jira.parseXML( output );
756 getLog().info( "The JIRA version is '" + jira.getJiraVersion() + "'" );
757 return jira.getIssueList();
758 }
759 else
760 {
761 getLog().warn( "JIRA file " + output.getPath() + " doesn't exist." );
762 return Collections.emptyList();
763 }
764 }
765
766 public void setJiraDatePattern( String jiraDatePattern )
767 {
768 this.jiraDatePattern = jiraDatePattern;
769 }
770
771
772
773
774
775
776 public void setOutput( File thisOutput )
777 {
778 this.output = thisOutput;
779 }
780
781 public File getOutput()
782 {
783 return this.output;
784 }
785
786
787
788
789
790
791 public void setMavenProject( Object thisProject )
792 {
793 this.project = (MavenProject) thisProject;
794 }
795
796
797
798
799
800
801 public void setNbEntries( final int nbEntries )
802 {
803 nbEntriesMax = nbEntries;
804 }
805
806
807
808
809
810
811 public void setStatusIds( String thisStatusIds )
812 {
813 statusIds = thisStatusIds;
814 }
815
816
817
818
819
820
821 public void setPriorityIds( String thisPriorityIds )
822 {
823 priorityIds = thisPriorityIds;
824 }
825
826
827
828
829
830
831 public void setResolutionIds( String thisResolutionIds )
832 {
833 resolutionIds = thisResolutionIds;
834 }
835
836
837
838
839
840
841 public void setSortColumnNames( String thisSortColumnNames )
842 {
843 sortColumnNames = thisSortColumnNames;
844 }
845
846
847
848
849
850
851 public void setWebPassword( String thisWebPassword )
852 {
853 this.webPassword = thisWebPassword;
854 }
855
856
857
858
859
860
861 public void setWebUser( String thisWebUser )
862 {
863 this.webUser = thisWebUser;
864 }
865
866
867
868
869
870
871 public void setJiraPassword( final String thisJiraPassword )
872 {
873 this.jiraPassword = thisJiraPassword;
874 }
875
876
877
878
879
880
881 public void setJiraUser( String thisJiraUser )
882 {
883 this.jiraUser = thisJiraUser;
884 }
885
886
887
888
889
890
891 public void setFilter( String thisFilter )
892 {
893 this.filter = thisFilter;
894 }
895
896
897
898
899
900
901 public void setComponent( String theseComponents )
902 {
903 this.component = theseComponents;
904 }
905
906
907
908
909
910
911 public void setFixVersionIds( String theseFixVersionIds )
912 {
913 this.fixVersionIds = theseFixVersionIds;
914 }
915
916
917
918
919
920
921 public void setTypeIds( String theseTypeIds )
922 {
923 typeIds = theseTypeIds;
924 }
925
926 public void setLog( Log log )
927 {
928 this.log = log;
929 }
930
931 private Log getLog()
932 {
933 return log;
934 }
935
936 public void setSettings( Settings settings )
937 {
938 this.settings = settings;
939 }
940 }