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