diff --git a/src/main/java/org/jboss/logmanager/handlers/ClientSocketFactory.java b/src/main/java/org/jboss/logmanager/handlers/ClientSocketFactory.java new file mode 100644 index 00000000..7e8d1503 --- /dev/null +++ b/src/main/java/org/jboss/logmanager/handlers/ClientSocketFactory.java @@ -0,0 +1,134 @@ +/* + * JBoss, Home of Professional Open Source. + * + * Copyright 2018 Red Hat, Inc., and individual contributors + * as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jboss.logmanager.handlers; + +import java.io.IOException; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import javax.net.SocketFactory; + +/** + * A factory used to create writable sockets. + * + * @author James R. Perkins + */ +public interface ClientSocketFactory { + + /** + * Creates a datagram socket for UDP communication. + * + * @return the newly created socket + * + * @throws SocketException if binding the socket fails + */ + DatagramSocket createDatagramSocket() throws SocketException; + + /** + * Creates a TCP socket. + * + * @return the newly created socket + * + * @throws IOException if an error occurs creating the socket + */ + Socket createSocket() throws IOException; + + /** + * Returns the address being used to create sockets. + * + * @return the address being used + */ + InetAddress getAddress(); + + /** + * Returns the port being used to create sockets. + * + * @return the port being used + */ + int getPort(); + + /** + * A convenience method to return the socket address. + *
+ * The default implementation simply returns {@code new InetSocketAddress(getAddress(), getPort())}. + *
+ * + * @return a socket address + */ + default SocketAddress getSocketAddress() { + return new InetSocketAddress(getAddress(), getPort()); + } + + /** + * Creates a new default implementation of the factory which uses {@link SocketFactory#getDefault()} for TCP + * sockets and {@code new DatagramSocket()} for UDP sockets. + * + * @param address the address to bind to + * @param port the port to bind to + * + * @return the client socket factory + */ + static ClientSocketFactory of(final InetAddress address, final int port) { + return of(SocketFactory.getDefault(), address, port); + } + + /** + * Creates a new default implementation of the factory which uses the provided + * {@linkplain SocketFactory#createSocket(InetAddress, int) socket factory} to create TCP connections and + * {@code new DatagramSocket()} for UDP sockets. + * + * @param socketFactory the socket factory used for TCP connections, if {@code null} the + * {@linkplain SocketFactory#getDefault() default} socket factory will be used + * @param address the address to bind to + * @param port the port to bind to + * + * @return the client socket factory + */ + static ClientSocketFactory of(final SocketFactory socketFactory, final InetAddress address, final int port) { + if (address == null || port < 0) { + throw new IllegalArgumentException(String.format("The address cannot be null (%s) and the port must be a positive integer (%d)", address, port)); + } + final SocketFactory factory = (socketFactory == null ? SocketFactory.getDefault() : socketFactory); + return new ClientSocketFactory() { + @Override + public DatagramSocket createDatagramSocket() throws SocketException { + return new DatagramSocket(); + } + + @Override + public Socket createSocket() throws IOException { + return factory.createSocket(address, port); + } + + @Override + public InetAddress getAddress() { + return address; + } + + @Override + public int getPort() { + return port; + } + }; + } +} diff --git a/src/main/java/org/jboss/logmanager/handlers/SocketHandler.java b/src/main/java/org/jboss/logmanager/handlers/SocketHandler.java index a1b4d67d..798ae9bc 100644 --- a/src/main/java/org/jboss/logmanager/handlers/SocketHandler.java +++ b/src/main/java/org/jboss/logmanager/handlers/SocketHandler.java @@ -68,6 +68,7 @@ public enum Protocol { private final Object outputLock = new Object(); // All the following fields are guarded by outputLock + private ClientSocketFactory clientSocketFactory; private SocketFactory socketFactory; private InetAddress address; private int port; @@ -143,6 +144,7 @@ public SocketHandler(final Protocol protocol, final InetAddress address, final i * @param port the port to connect to * * @throws UnknownHostException if an error occurs resolving the hostname + * @see #SocketHandler(ClientSocketFactory, Protocol) */ public SocketHandler(final SocketFactory socketFactory, final Protocol protocol, final String hostname, final int port) throws UnknownHostException { this(socketFactory, protocol, InetAddress.getByName(hostname), port); @@ -157,14 +159,35 @@ public SocketHandler(final SocketFactory socketFactory, final Protocol protocol, * @param protocol the protocol to connect with * @param address the address to connect to * @param port the port to connect to + * + * @see #SocketHandler(ClientSocketFactory, Protocol) */ public SocketHandler(final SocketFactory socketFactory, final Protocol protocol, final InetAddress address, final int port) { + this.socketFactory = socketFactory; + this.clientSocketFactory = null; this.address = address; this.port = port; - this.protocol = protocol; + this.protocol = (protocol == null ? Protocol.TCP : protocol); + initialize = true; + writer = null; + blockOnReconnect = false; + } + + /** + * Creates a socket handler. + * + * @param clientSocketFactory the client socket factory used to create sockets + * @param protocol the protocol to connect with + */ + public SocketHandler(final ClientSocketFactory clientSocketFactory, final Protocol protocol) { + this.clientSocketFactory = clientSocketFactory; + if (clientSocketFactory != null) { + address = clientSocketFactory.getAddress(); + port = clientSocketFactory.getPort(); + } + this.protocol = (protocol == null ? Protocol.TCP : protocol); initialize = true; writer = null; - this.socketFactory = socketFactory; blockOnReconnect = false; } @@ -229,19 +252,28 @@ public InetAddress getAddress() { /** * Sets the address to connect to. + *+ * Note that is resets the {@linkplain #setClientSocketFactory(ClientSocketFactory) client socket factory}. + *
* * @param address the address */ public void setAddress(final InetAddress address) { checkAccess(this); synchronized (outputLock) { + if (!this.address.equals(address)) { + initialize = true; + clientSocketFactory = null; + } this.address = address; - initialize = true; } } /** * Sets the address to connect to by doing a lookup on the hostname. + *+ * Note that is resets the {@linkplain #setClientSocketFactory(ClientSocketFactory) client socket factory}. + *
* * @param hostname the host name used to resolve the address * @@ -277,6 +309,7 @@ public void setBlockOnReconnect(final boolean blockOnReconnect) { checkAccess(this); synchronized (outputLock) { this.blockOnReconnect = blockOnReconnect; + initialize = true; } } @@ -293,7 +326,7 @@ public Protocol getProtocol() { * Sets the protocol to use. If the value is {@code null} the protocol will be set to * {@linkplain Protocol#TCP TCP}. *- * Note that is resets the {@linkplain #setSocketFactory(SocketFactory) socket factory}. + * Note that is resets the {@linkplain #setSocketFactory(SocketFactory) socket factory} if it was previously set. *
* * @param protocol the protocol to use @@ -304,10 +337,11 @@ public void setProtocol(final Protocol protocol) { if (protocol == null) { this.protocol = Protocol.TCP; } - // Reset the socket factory - socketFactory = null; + if (this.protocol != protocol) { + socketFactory = null; + initialize = true; + } this.protocol = protocol; - initialize = true; } } @@ -322,14 +356,20 @@ public int getPort() { /** * Sets the port to connect to. + *+ * Note that is resets the {@linkplain #setClientSocketFactory(ClientSocketFactory) client socket factory}. + *
* * @param port the port */ public void setPort(final int port) { checkAccess(this); synchronized (outputLock) { + if (this.port != port) { + initialize = true; + clientSocketFactory = null; + } this.port = port; - initialize = true; } } @@ -338,15 +378,33 @@ public void setPort(final int port) { * connections. ** Note that if the {@linkplain #setProtocol(Protocol) protocol} is set the socket factory will be set to - * {@code null} and reset. + * {@code null} and reset. Setting a value here also resets the + * {@linkplain #setClientSocketFactory(ClientSocketFactory) client socket factory}. *
* * @param socketFactory the socket factory + * + * @see #setClientSocketFactory(ClientSocketFactory) */ public void setSocketFactory(final SocketFactory socketFactory) { checkAccess(this); synchronized (outputLock) { this.socketFactory = socketFactory; + this.clientSocketFactory = null; + initialize = true; + } + } + + /** + * Sets the client socket factory used to create sockets. If {@code null} the + * {@linkplain #setAddress(InetAddress) address} and {@linkplain #setPort(int) port} are required to be set. + * + * @param clientSocketFactory the client socket factory to use + */ + public void setClientSocketFactory(final ClientSocketFactory clientSocketFactory) { + checkAccess(this); + synchronized (outputLock) { + this.clientSocketFactory = clientSocketFactory; initialize = true; } } @@ -388,19 +446,11 @@ private void initialize() { private OutputStream createOutputStream() { if (address != null || port >= 0) { try { + final ClientSocketFactory socketFactory = getClientSocketFactory(); if (protocol == Protocol.UDP) { - return new UdpOutputStream(address, port); - } - SocketFactory socketFactory = this.socketFactory; - if (socketFactory == null) { - if (protocol == Protocol.SSL_TCP) { - this.socketFactory = socketFactory = SSLSocketFactory.getDefault(); - } else { - // Assume we want a TCP connection - this.socketFactory = socketFactory = SocketFactory.getDefault(); - } + return new UdpOutputStream(socketFactory); } - return new TcpOutputStream(socketFactory, address, port, blockOnReconnect); + return new TcpOutputStream(socketFactory, blockOnReconnect); } catch (IOException e) { reportError("Failed to create socket output stream", e, ErrorManager.OPEN_FAILURE); } @@ -408,6 +458,28 @@ private OutputStream createOutputStream() { return null; } + private ClientSocketFactory getClientSocketFactory() { + synchronized (outputLock) { + if (clientSocketFactory != null) { + return clientSocketFactory; + } + if (address == null || port <= 0) { + throw new IllegalStateException("An address and port greater than 0 is required."); + } + final ClientSocketFactory clientSocketFactory; + if (socketFactory == null) { + if (protocol == Protocol.SSL_TCP) { + clientSocketFactory = ClientSocketFactory.of(SSLSocketFactory.getDefault(), address, port); + } else { + clientSocketFactory = ClientSocketFactory.of(address, port); + } + } else { + clientSocketFactory = ClientSocketFactory.of(socketFactory, address, port); + } + return clientSocketFactory; + } + } + private void writeHead(final Writer writer) { try { final Formatter formatter = getFormatter(); diff --git a/src/main/java/org/jboss/logmanager/handlers/SyslogHandler.java b/src/main/java/org/jboss/logmanager/handlers/SyslogHandler.java index 9146083e..8d3e01cf 100644 --- a/src/main/java/org/jboss/logmanager/handlers/SyslogHandler.java +++ b/src/main/java/org/jboss/logmanager/handlers/SyslogHandler.java @@ -35,6 +35,8 @@ import java.util.logging.Formatter; import java.util.logging.Level; import java.util.regex.Pattern; +import javax.net.SocketFactory; +import javax.net.ssl.SSLSocketFactory; import org.jboss.logmanager.ExtHandler; import org.jboss.logmanager.ExtLogRecord; @@ -324,6 +326,7 @@ public static enum SyslogType { private boolean truncate; private int maxLen; private boolean blockOnReconnect; + private ClientSocketFactory clientSocketFactory; /** * The default class constructor. @@ -667,6 +670,19 @@ public void setBlockOnReconnect(final boolean blockOnReconnect) { } } + /** + * Sets the client socket factory used to create sockets. + * + * @param clientSocketFactory the client socket factory to use + */ + public void setClientSocketFactory(final ClientSocketFactory clientSocketFactory) { + checkAccess(this); + synchronized (outputLock) { + this.clientSocketFactory = clientSocketFactory; + initializeConnection = true; + } + } + /** * Checks whether or not characters below decimal 32, traditional US-ASCII control values expect {@code DEL}, are * being escaped or not. @@ -1084,14 +1100,11 @@ private void init() { final OutputStream out; // Check the sockets try { - if (protocol == Protocol.TCP) { - out = new TcpOutputStream(serverAddress, port, blockOnReconnect); - } else if (protocol == Protocol.UDP) { - out = new UdpOutputStream(serverAddress, port); - } else if (protocol == Protocol.SSL_TCP) { - out = new SslTcpOutputStream(serverAddress, port, blockOnReconnect); + final ClientSocketFactory clientSocketFactory = getClientSocketFactory(); + if (protocol == Protocol.UDP) { + out = new UdpOutputStream(clientSocketFactory); } else { - throw new IllegalStateException("Invalid protocol: " + protocol); + out = new TcpOutputStream(clientSocketFactory, blockOnReconnect); } setOutputStream(out, false); } catch (IOException e) { @@ -1296,6 +1309,16 @@ protected byte[] createRFC3164Header(final ExtLogRecord record) throws IOExcepti return buffer.toArray(); } + private ClientSocketFactory getClientSocketFactory() { + synchronized (outputLock) { + if (clientSocketFactory != null) { + return clientSocketFactory; + } + final SocketFactory socketFactory = (protocol == Protocol.SSL_TCP ? SSLSocketFactory.getDefault() : SocketFactory.getDefault()); + return ClientSocketFactory.of(socketFactory, serverAddress, port); + } + } + private static String checkPrintableAscii(final String name, final String value) { if (value != null && PRINTABLE_ASCII_PATTERN.matcher(value).find()) { final String upper = Character.toUpperCase(name.charAt(0)) + name.substring(1); diff --git a/src/main/java/org/jboss/logmanager/handlers/TcpOutputStream.java b/src/main/java/org/jboss/logmanager/handlers/TcpOutputStream.java index df701d14..fb2b6dae 100644 --- a/src/main/java/org/jboss/logmanager/handlers/TcpOutputStream.java +++ b/src/main/java/org/jboss/logmanager/handlers/TcpOutputStream.java @@ -54,9 +54,7 @@ public class TcpOutputStream extends OutputStream implements FlushableCloseable protected final Object outputLock = new Object(); - private final SocketFactory socketFactory; - private final InetAddress address; - private final int port; + private final ClientSocketFactory socketFactory; private final Deque