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