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 }