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ülcü
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ülcü
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 }