View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.log4j.net;
19  
20  import org.apache.log4j.plugins.Plugin;
21  import org.apache.log4j.plugins.Receiver;
22  import org.apache.log4j.spi.LoggerRepository;
23  
24  import java.io.IOException;
25  import java.net.Socket;
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.Iterator;
29  import java.util.List;
30  
31  /**
32    SocketHubReceiver receives a remote logging event on a configured
33    socket and "posts" it to a LoggerRepository as if the event was
34    generated locally. This class is designed to receive events from
35    the SocketHubAppender class (or classes that send compatible events).
36  
37    <p>Once the event has been "posted", it will be handled by the
38    appenders currently configured in the LoggerRespository.
39  
40    @author Mark Womack
41    @author Ceki G&uuml;lc&uuml;
42    @author Paul Smith (psmith@apache.org)
43  */
44  public class SocketHubReceiver
45  extends Receiver implements SocketNodeEventListener, PortBased {
46  
47      /**
48       * Default reconnection delay.
49       */
50    static final int DEFAULT_RECONNECTION_DELAY   = 30000;
51  
52      /**
53       * Host.
54       */
55    protected String host;
56  
57      /**
58       * Port.
59       */
60    protected int port;
61      /**
62       * Reconnection delay.
63       */
64    protected int reconnectionDelay = DEFAULT_RECONNECTION_DELAY;
65  
66    /**
67     * The MulticastDNS zone advertised by a SocketHubReceiver
68     */
69    public static final String ZONE = "_log4j_obj_tcpconnect_receiver.local.";
70  
71      /**
72       * Active.
73       */
74    protected boolean active = false;
75  
76      /**
77       * Connector.
78       */
79    protected Connector connector;
80  
81      /**
82       * Socket.
83       */
84    protected SocketNode13 socketNode;
85  
86      /**
87       * Listener list.
88       */
89    private List listenerList = Collections.synchronizedList(new ArrayList());
90  
91    private boolean advertiseViaMulticastDNS;
92    private ZeroConfSupport zeroConf;
93  
94      /**
95       * Create new instance.
96       */
97    public SocketHubReceiver() {
98       super();
99    }
100 
101     /**
102      * Create new instance.
103      * @param h host
104      * @param p port
105      */
106   public SocketHubReceiver(final String h,
107                            final int p) {
108     super();
109     host = h;
110     port = p;
111   }
112 
113     /**
114      * Create new instance.
115      * @param h host
116      * @param p port
117      * @param repo logger repository
118      */
119   public SocketHubReceiver(final String h,
120                            final int p,
121                            final LoggerRepository repo) {
122     super();
123     host = h;
124     port = p;
125     repository = repo;
126   }
127 
128   /**
129    * Adds a SocketNodeEventListener to this receiver to be notified
130    * of SocketNode events.
131    * @param l listener
132    */
133   public void addSocketNodeEventListener(final SocketNodeEventListener l) {
134     listenerList.add(l);
135   }
136 
137   /**
138    * Removes a specific SocketNodeEventListener from this instance
139    * so that it will no  longer be notified of SocketNode events.
140    * @param l listener
141    */
142   public void removeSocketNodeEventListener(
143           final SocketNodeEventListener l) {
144     listenerList.remove(l);
145   }
146 
147   /**
148     Get the remote host to connect to for logging events.
149     @return host
150    */
151   public String getHost() {
152     return host;
153   }
154 
155   /**
156    * Configures the Host property, this will require activateOptions
157    * to be called for this to take effect.
158    * @param remoteHost address of remote host.
159    */
160   public void setHost(final String remoteHost) {
161     this.host = remoteHost;
162   }
163   /**
164     Set the remote host to connect to for logging events.
165    Equivalent to setHost.
166    @param remoteHost address of remote host.
167    */
168   public void setPort(final String remoteHost) {
169     host = remoteHost;
170   }
171 
172   /**
173     Get the remote port to connect to for logging events.
174    @return port
175    */
176   public int getPort() {
177     return port;
178   }
179 
180   /**
181     Set the remote port to connect to for logging events.
182     @param p port
183    */
184   public void setPort(final int p) {
185     this.port = p;
186   }
187 
188   /**
189      The <b>ReconnectionDelay</b> option takes a positive integer
190      representing the number of milliseconds to wait between each
191      failed connection attempt to the server. The default value of
192      this option is 30000 which corresponds to 30 seconds.
193 
194      <p>Setting this option to zero turns off reconnection
195      capability.
196    @param delay milliseconds to wait or zero to not reconnect.
197    */
198   public void setReconnectionDelay(final int delay) {
199     int oldValue = this.reconnectionDelay;
200     this.reconnectionDelay = delay;
201     firePropertyChange("reconnectionDelay", oldValue, this.reconnectionDelay);
202   }
203 
204   /**
205      Returns value of the <b>ReconnectionDelay</b> option.
206    @return value of reconnection delay option.
207    */
208   public int getReconnectionDelay() {
209     return reconnectionDelay;
210   }
211 
212   /**
213    * Returns true if the receiver is the same class and they are
214    * configured for the same properties, and super class also considers
215    * them to be equivalent. This is used by PluginRegistry when determining
216    * if the a similarly configured receiver is being started.
217    *
218    * @param testPlugin The plugin to test equivalency against.
219    * @return boolean True if the testPlugin is equivalent to this plugin.
220    */
221   public boolean isEquivalent(final Plugin testPlugin) {
222     if (testPlugin != null && testPlugin instanceof SocketHubReceiver) {
223       SocketHubReceiver sReceiver = (SocketHubReceiver) testPlugin;
224 
225       return (port == sReceiver.getPort()
226               && host.equals(sReceiver.getHost())
227               && reconnectionDelay == sReceiver.getReconnectionDelay()
228               && super.isEquivalent(testPlugin));
229     }
230     return false;
231   }
232 
233   /**
234     Sets the flag to indicate if receiver is active or not.
235    @param b new value
236    */
237   protected synchronized void setActive(final boolean b) {
238     active = b;
239   }
240 
241   /**
242     Starts the SocketReceiver with the current options. */
243   public void activateOptions() {
244     if (!isActive()) {
245       setActive(true);
246       if (advertiseViaMulticastDNS) {
247         zeroConf = new ZeroConfSupport(ZONE, port, getName());
248         zeroConf.advertise();
249       }
250 
251       fireConnector(false);
252     }
253   }
254 
255   /**
256     Called when the receiver should be stopped. Closes the socket */
257   public synchronized void shutdown() {
258     // mark this as no longer running
259     active = false;
260 
261     // close the socket
262     try {
263       if (socketNode != null) {
264         socketNode.close();
265         socketNode = null;
266       }
267     } catch (Exception e) {
268       getLogger().info("Excpetion closing socket", e);
269       // ignore for now
270     }
271 
272     // stop the connector
273     if (connector != null) {
274       connector.interrupted = true;
275       connector = null;  // allow gc
276     }
277     if (advertiseViaMulticastDNS) {
278         zeroConf.unadvertise();
279     }
280   }
281 
282   /**
283     Listen for a socketClosedEvent from the SocketNode. Reopen the
284     socket if this receiver is still active.
285    @param e exception not used.
286    */
287   public void socketClosedEvent(final Exception e) {
288     // if it is a non-normal closed event
289     // we clear the connector object here
290     // so that it actually does reconnect if the
291     // remote socket dies.
292     if (e != null) {
293       connector = null;
294       fireConnector(true);
295     }
296   }
297 
298     /**
299      * Fire connectors.
300      * @param isReconnect true if reconnect.
301      */
302   private synchronized void fireConnector(final boolean isReconnect) {
303     if (active && connector == null) {
304       getLogger().debug("Starting a new connector thread.");
305       connector = new Connector(isReconnect);
306       connector.setDaemon(true);
307       connector.setPriority(Thread.MIN_PRIORITY);
308       connector.start();
309     }
310   }
311 
312     /**
313      * Set socket.
314      * @param newSocket new value for socket.
315      */
316   private synchronized void setSocket(final Socket newSocket) {
317     connector = null;
318     socketNode = new SocketNode13(newSocket, this);
319     socketNode.addSocketNodeEventListener(this);
320 
321     synchronized (listenerList) {
322         for (Iterator iter = listenerList.iterator(); iter.hasNext();) {
323             SocketNodeEventListener listener =
324                     (SocketNodeEventListener) iter.next();
325             socketNode.addSocketNodeEventListener(listener);
326         }
327     }
328     new Thread(socketNode).start();
329   }
330 
331   public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
332       this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
333   }
334 
335   public boolean isAdvertiseViaMulticastDNS() {
336       return advertiseViaMulticastDNS;
337   }
338 
339   /**
340    The Connector will reconnect when the server becomes available
341    again.  It does this by attempting to open a new connection every
342    <code>reconnectionDelay</code> milliseconds.
343 
344    <p>It stops trying whenever a connection is established. It will
345    restart to try reconnect to the server when previpously open
346    connection is droppped.
347 
348    @author  Ceki G&uuml;lc&uuml;
349    */
350   private final class Connector extends Thread {
351 
352       /**
353        * Interruption status.
354        */
355     boolean interrupted = false;
356       /**
357        * If true, then delay on next iteration.
358        */
359     boolean doDelay;
360 
361       /**
362        * Create new instance.
363        * @param isReconnect true if reconnecting.
364        */
365     public Connector(final boolean isReconnect) {
366       super();
367       doDelay = isReconnect;
368     }
369 
370       /**
371        * Attempt to connect until interrupted.
372        */
373     public void run() {
374       while (!interrupted) {
375         try {
376           if (doDelay) {
377             getLogger().debug("waiting for " + reconnectionDelay
378               + " milliseconds before reconnecting.");
379             sleep(reconnectionDelay);
380           }
381           doDelay = true;
382           getLogger().debug("Attempting connection to " + host);
383           Socket s = new Socket(host, port);
384           setSocket(s);
385           getLogger().debug(
386                   "Connection established. Exiting connector thread.");
387           break;
388         } catch (InterruptedException e) {
389           getLogger().debug("Connector interrupted. Leaving loop.");
390           return;
391         } catch (java.net.ConnectException e) {
392           getLogger().debug("Remote host {} refused connection.", host);
393         } catch (IOException e) {
394           getLogger().debug("Could not connect to {}. Exception is {}.",
395                   host, e);
396         }
397       }
398     }
399   }
400 
401     /**
402      * This method does nothing.
403      * @param remoteInfo remote info.
404      */
405   public void socketOpened(final String remoteInfo) {
406 
407     // This method does nothing.
408   }
409 }