View Javadoc

1   package org.apache.maven.continuum.notification.irc;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.continuum.model.project.ProjectScmRoot;
23  import org.apache.maven.continuum.configuration.ConfigurationService;
24  import org.apache.maven.continuum.model.project.BuildDefinition;
25  import org.apache.maven.continuum.model.project.BuildResult;
26  import org.apache.maven.continuum.model.project.Project;
27  import org.apache.maven.continuum.model.project.ProjectNotifier;
28  import org.apache.maven.continuum.notification.AbstractContinuumNotifier;
29  import org.apache.maven.continuum.notification.ContinuumNotificationDispatcher;
30  import org.apache.maven.continuum.notification.MessageContext;
31  import org.apache.maven.continuum.notification.NotificationException;
32  import org.codehaus.plexus.personality.plexus.lifecycle.phase.Disposable;
33  import org.codehaus.plexus.util.StringUtils;
34  import org.schwering.irc.lib.IRCConnection;
35  import org.schwering.irc.lib.IRCConstants;
36  import org.schwering.irc.lib.IRCEventListener;
37  import org.schwering.irc.lib.IRCModeParser;
38  import org.schwering.irc.lib.IRCUser;
39  import org.schwering.irc.lib.ssl.SSLDefaultTrustManager;
40  import org.schwering.irc.lib.ssl.SSLIRCConnection;
41  import org.slf4j.Logger;
42  import org.slf4j.LoggerFactory;
43  import org.springframework.stereotype.Service;
44  
45  import java.io.IOException;
46  import java.util.ArrayList;
47  import java.util.HashMap;
48  import java.util.List;
49  import java.util.Map;
50  import javax.annotation.Resource;
51  
52  /**
53   * <b>This implementation assumes there aren't concurrent acces to the IRCConnection</b>
54   *
55   * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
56   * @version $Id: IrcContinuumNotifier.java 1372260 2012-08-13 04:29:09Z brett $
57   */
58  @Service( "notifier#irc" )
59  public class IrcContinuumNotifier
60      extends AbstractContinuumNotifier
61      implements Disposable
62  {
63      private static final Logger log = LoggerFactory.getLogger( IrcContinuumNotifier.class );
64  
65      // ----------------------------------------------------------------------
66      // Requirements
67      // ----------------------------------------------------------------------
68  
69      @Resource
70      private ConfigurationService configurationService;
71  
72      private int defaultPort = 6667;
73  
74      /**
75       * key is upper(hostname) + port + upper(nick) + upper(alternateNick)
76       */
77      private Map<String, IRCConnection> hostConnections = new HashMap<String, IRCConnection>();
78  
79      private Map<String, List<String>> channelConnections = new HashMap<String, List<String>>();
80  
81  
82      // ----------------------------------------------------------------------
83      // Plexus Lifecycle
84      // ----------------------------------------------------------------------    
85      public void dispose()
86      {
87          // cleanup connections
88          for ( String key : hostConnections.keySet() )
89          {
90              IRCConnection connection = hostConnections.get( key );
91              if ( connection.isConnected() )
92              {
93                  connection.doQuit( "Continuum shutting down" );
94                  connection.close();
95              }
96          }
97  
98      }
99  
100     // ----------------------------------------------------------------------
101     // Internal connections 
102     // ----------------------------------------------------------------------    
103     private IRCConnection getIRConnection( String host, int port, String password, String nick, String alternateNick,
104                                            String userName, String realName, String channel, boolean ssl )
105         throws IOException
106     {
107         String key = getConnectionKey( host, port, nick, alternateNick );
108         IRCConnection conn = hostConnections.get( key );
109         if ( conn != null )
110         {
111             checkConnection( conn, key );
112             return conn;
113         }
114 
115         if ( !ssl )
116         {
117             conn = new IRCConnection( host, new int[]{port}, password, nick, userName, realName );
118         }
119         else
120         {
121             conn = new SSLIRCConnection( host, new int[]{port}, password, nick, userName, realName );
122             ( (SSLIRCConnection) conn ).addTrustManager( new SSLDefaultTrustManager() );
123         }
124 
125         conn.addIRCEventListener( new Listener( conn, nick, alternateNick ) );
126         checkConnection( conn, key );
127         checkChannel( conn, key, channel );
128         hostConnections.put( key, conn );
129         return conn;
130     }
131 
132     private String getConnectionKey( String host, int port, String nick, String alternateNick )
133     {
134         String nickname = nick;
135         String alternateNickName = alternateNick;
136         if ( nick == null )
137         {
138             nickname = "null";
139         }
140         if ( alternateNick == null )
141         {
142             alternateNickName = "null";
143         }
144         return host.toUpperCase() + Integer.toString( port ) + nickname.toUpperCase() + alternateNickName.toUpperCase();
145     }
146 
147     private void checkConnection( IRCConnection conn, String key )
148         throws IOException
149     {
150         if ( !conn.isConnected() )
151         {
152             conn.connect();
153             //required for some servers that are slow to initialise the connection, in most of case, servers with auth 
154             try
155             {
156                 Thread.sleep( 5000 );
157             }
158             catch ( InterruptedException e )
159             {
160                 //nothing to do
161             }
162 
163             //join to all channels
164             List<String> channels = channelConnections.get( key );
165             if ( channels != null )
166             {
167                 for ( String channel : channels )
168                 {
169                     connectToChannel( conn, channel );
170                 }
171             }
172         }
173     }
174 
175     private void checkChannel( IRCConnection conn, String key, String channel )
176     {
177         List<String> channels = channelConnections.get( key );
178         if ( channels == null )
179         {
180             connectToChannel( conn, channel );
181             channels = new ArrayList<String>();
182             channels.add( channel );
183             channelConnections.put( key, channels );
184         }
185         else
186         {
187             boolean found = false;
188             for ( String c : channels )
189             {
190                 if ( c.equalsIgnoreCase( channel ) )
191                 {
192                     found = true;
193                 }
194             }
195             if ( !found )
196             {
197                 channels.add( channel );
198                 channelConnections.put( key, channels );
199             }
200 
201             //reconnect unconditionally
202             connectToChannel( conn, channel );
203         }
204     }
205 
206     private void connectToChannel( IRCConnection conn, String channel )
207     {
208         conn.doJoin( channel );
209     }
210 
211     // ----------------------------------------------------------------------
212     // Notifier Implementation
213     // ----------------------------------------------------------------------
214 
215     public String getType()
216     {
217         return "irc";
218     }
219 
220     public void sendMessage( String messageId, MessageContext context )
221         throws NotificationException
222     {
223         Project project = context.getProject();
224 
225         List<ProjectNotifier> notifiers = context.getNotifiers();
226 
227         BuildDefinition buildDefinition = context.getBuildDefinition();
228 
229         BuildResult build = context.getBuildResult();
230 
231         ProjectScmRoot projectScmRoot = context.getProjectScmRoot();
232 
233         boolean isPrepareBuildComplete = messageId.equals(
234             ContinuumNotificationDispatcher.MESSAGE_ID_PREPARE_BUILD_COMPLETE );
235 
236         if ( projectScmRoot == null && isPrepareBuildComplete )
237         {
238             return;
239         }
240 
241         // ----------------------------------------------------------------------
242         // If there wasn't any building done, don't notify
243         // ----------------------------------------------------------------------
244 
245         if ( build == null && !isPrepareBuildComplete )
246         {
247             return;
248         }
249 
250         // ----------------------------------------------------------------------
251         // Generate and send message
252         // ----------------------------------------------------------------------
253 
254         if ( messageId.equals( ContinuumNotificationDispatcher.MESSAGE_ID_BUILD_COMPLETE ) )
255         {
256             for ( ProjectNotifier notifier : notifiers )
257             {
258                 buildComplete( project, notifier, build, buildDefinition );
259             }
260         }
261         else if ( isPrepareBuildComplete )
262         {
263             for ( ProjectNotifier notifier : notifiers )
264             {
265                 prepareBuildComplete( projectScmRoot, notifier );
266             }
267         }
268     }
269 
270     private void buildComplete( Project project, ProjectNotifier projectNotifier, BuildResult build,
271                                 BuildDefinition buildDef )
272         throws NotificationException
273     {
274         // ----------------------------------------------------------------------
275         // Check if the message should be sent at all
276         // ----------------------------------------------------------------------
277 
278         BuildResult previousBuild = getPreviousBuild( project, buildDef, build );
279 
280         if ( !shouldNotify( build, previousBuild, projectNotifier ) )
281         {
282             return;
283         }
284 
285         sendMessage( projectNotifier.getConfiguration(), generateMessage( project, build, configurationService ) );
286     }
287 
288     private void prepareBuildComplete( ProjectScmRoot projectScmRoot, ProjectNotifier projectNotifier )
289         throws NotificationException
290     {
291         // ----------------------------------------------------------------------
292         // Check if the message should be sent at all
293         // ----------------------------------------------------------------------
294 
295         if ( !shouldNotify( projectScmRoot, projectNotifier ) )
296         {
297             return;
298         }
299 
300         sendMessage( projectNotifier.getConfiguration(), generateMessage( projectScmRoot, configurationService ) );
301     }
302 
303     private void sendMessage( Map<String, String> configuration, String message )
304         throws NotificationException
305     {
306         // ----------------------------------------------------------------------
307         // Gather configuration values
308         // ----------------------------------------------------------------------
309 
310         String host = configuration.get( "host" );
311 
312         String portAsString = configuration.get( "port" );
313         int port = defaultPort;
314         if ( portAsString != null )
315         {
316             port = Integer.parseInt( portAsString );
317         }
318         String channel = configuration.get( "channel" );
319 
320         String nickName = configuration.get( "nick" );
321 
322         if ( StringUtils.isEmpty( nickName ) )
323         {
324             nickName = "continuum";
325         }
326 
327         String alternateNickName = configuration.get( "alternateNick" );
328 
329         if ( StringUtils.isEmpty( alternateNickName ) )
330         {
331             alternateNickName = "continuum_";
332         }
333 
334         String userName = configuration.get( "username" );
335 
336         if ( StringUtils.isEmpty( userName ) )
337         {
338             userName = nickName;
339         }
340 
341         String fullName = configuration.get( "fullName" );
342 
343         if ( StringUtils.isEmpty( fullName ) )
344         {
345             fullName = nickName;
346         }
347 
348         String password = configuration.get( "password" );
349 
350         boolean isSsl = Boolean.parseBoolean( configuration.get( "ssl" ) );
351 
352         try
353         {
354             IRCConnection ircConnection = getIRConnection( host, port, password, nickName, alternateNickName, userName,
355                                                            fullName, channel, isSsl );
356             ircConnection.doPrivmsg( channel, message );
357         }
358         catch ( IOException e )
359         {
360             throw new NotificationException( "Exception while checkConnection to irc ." + host, e );
361         }
362     }
363 
364     /**
365      * Treats IRC events. The most of them are just printed.
366      */
367     class Listener
368         implements IRCEventListener
369     {
370         private String nick;
371 
372         private String alternateNick;
373 
374         private IRCConnection conn;
375 
376         public Listener( IRCConnection conn, String nick, String alternateNick )
377         {
378             this.conn = conn;
379             this.nick = nick;
380             this.alternateNick = alternateNick;
381         }
382 
383         public void onRegistered()
384         {
385             log.info( "Connected" );
386         }
387 
388         public void onDisconnected()
389         {
390             log.info( "Disconnected" );
391         }
392 
393         public void onError( String msg )
394         {
395             log.error( "Error: " + msg );
396         }
397 
398         public void onError( int num, String msg )
399         {
400             log.error( "Error #" + num + ": " + msg );
401             if ( num == IRCConstants.ERR_NICKNAMEINUSE )
402             {
403                 if ( alternateNick != null )
404                 {
405                     log.info( "reconnection with alternate nick: '" + alternateNick + "'" );
406                     try
407                     {
408                         boolean ssl = false;
409                         if ( conn instanceof SSLIRCConnection )
410                         {
411                             ssl = true;
412                         }
413                         String key = getConnectionKey( conn.getHost(), conn.getPort(), nick, alternateNick );
414                         conn = getIRConnection( conn.getHost(), conn.getPort(), conn.getPassword(), alternateNick, null,
415                                                 conn.getUsername(), conn.getRealname(), "#foo", ssl );
416                         hostConnections.put( key, conn );
417                     }
418                     catch ( IOException e )
419                     {
420                         e.printStackTrace();
421                     }
422                 }
423             }
424         }
425 
426         public void onInvite( String chan, IRCUser u, String nickPass )
427         {
428             if ( log.isDebugEnabled() )
429             {
430                 log.debug( chan + "> " + u.getNick() + " invites " + nickPass );
431             }
432         }
433 
434         public void onJoin( String chan, IRCUser u )
435         {
436             if ( log.isDebugEnabled() )
437             {
438                 log.debug( chan + "> " + u.getNick() + " joins" );
439             }
440         }
441 
442         public void onKick( String chan, IRCUser u, String nickPass, String msg )
443         {
444             if ( log.isDebugEnabled() )
445             {
446                 log.debug( chan + "> " + u.getNick() + " kicks " + nickPass );
447             }
448         }
449 
450         public void onMode( IRCUser u, String nickPass, String mode )
451         {
452             if ( log.isDebugEnabled() )
453             {
454                 log.debug( "Mode: " + u.getNick() + " sets modes " + mode + " " + nickPass );
455             }
456         }
457 
458         public void onMode( String chan, IRCUser u, IRCModeParser mp )
459         {
460             if ( log.isDebugEnabled() )
461             {
462                 log.debug( chan + "> " + u.getNick() + " sets mode: " + mp.getLine() );
463             }
464         }
465 
466         public void onNick( IRCUser u, String nickNew )
467         {
468             if ( log.isDebugEnabled() )
469             {
470                 log.debug( "Nick: " + u.getNick() + " is now known as " + nickNew );
471             }
472         }
473 
474         public void onNotice( String target, IRCUser u, String msg )
475         {
476             log.info( target + "> " + u.getNick() + " (notice): " + msg );
477         }
478 
479         public void onPart( String chan, IRCUser u, String msg )
480         {
481             if ( log.isDebugEnabled() )
482             {
483                 log.debug( chan + "> " + u.getNick() + " parts" );
484             }
485         }
486 
487         public void onPrivmsg( String chan, IRCUser u, String msg )
488         {
489             if ( log.isDebugEnabled() )
490             {
491                 log.debug( chan + "> " + u.getNick() + ": " + msg );
492             }
493         }
494 
495         public void onQuit( IRCUser u, String msg )
496         {
497             if ( log.isDebugEnabled() )
498             {
499                 log.debug( "Quit: " + u.getNick() );
500             }
501         }
502 
503         public void onReply( int num, String value, String msg )
504         {
505             log.info( "Reply #" + num + ": " + value + " " + msg );
506         }
507 
508         public void onTopic( String chan, IRCUser u, String topic )
509         {
510             if ( log.isDebugEnabled() )
511             {
512                 log.debug( chan + "> " + u.getNick() + " changes topic into: " + topic );
513             }
514         }
515 
516         public void onPing( String p )
517         {
518             if ( log.isDebugEnabled() )
519             {
520                 log.debug( "Ping:" + p );
521             }
522         }
523 
524         public void unknown( String a, String b, String c, String d )
525         {
526             if ( log.isDebugEnabled() )
527             {
528                 log.debug( "UNKNOWN: " + a + " b " + c + " " + d );
529             }
530         }
531     }
532 }