All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jsr107.tck.support.Server Maven / Gradle / Ivy

Go to download

Tests for JSR107 compliant caches. These are tests from the JSR107 TCK that were published as Open Source. The artifact may also contain additional tests and improvements. For compliance testing use the original TCK as published via jcp.org.

The newest version!
/**
 *  Copyright 2011-2013 Terracotta, Inc.
 *  Copyright 2011-2013 Oracle, Inc.
 *  Copyright 2016 headissue GmbH
 *
 *  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.jsr107.tck.support;

import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A rudimentary multi-threaded {@link Socket}-based {@link Server} that can
 * handle, using {@link OperationHandler}s, {@link Operation}s invoked by
 * {@link Client}s.
 *
 * @author Brian Oliver
 * @author Jens Wilke
 * @see Client
 * @see Operation
 * @see OperationHandler
 */
public class Server implements AutoCloseable {

    /**
     * If false, always communicate over TCP and don't try to use direct method in the local VM
     */
    private static final boolean ALLOW_DIRECT_CALLS =
      System.getProperty("org.jsr107.tck.support.server.alwaysUseTcp") == null;

    /**
     * If a server and socket is unused, put it in a queue and reuse it.
     * This way we can save the TCP connection overhead and thread start if
     * it is possible to communicate directly within the local machine.
     */
    private static final Queue SOCKET_THREADS = new LinkedBlockingDeque<>(10);

    /**
     * Contains the servers that run on the respective port. The client can lookup its server
     * in the hash table. If it is present, it runs in the local VM. If it is not present
     * the client must connect via TCP.
     */
    private static final ConcurrentHashMap PORT_2_SERVER = new ConcurrentHashMap<>();

    /**
     * Returns the server for this port, if running in the local machine.
     */
    public static Server lookupServerAtLocalMachine(int port) {
        return PORT_2_SERVER.get(port);
    }

    /**
     * Logger
     */
    public static final  Logger LOG = Logger.getLogger(Server.class.getName());

    /**
     * Special operation to signal the server that the client has been closed.
    */
   public static final Operation CLOSE_OPERATION = new Operation() {

        public String getType() {
            return "CLOSE";
        }

       /**
       * The connections are closed by the server upon receiving the closed operation.
       * We need to block in the client until the server performed the close, to make
       * sure the server has removed the client from the client collection map.
       *
       * 

This is executed in the client after the close command is sent.  *  * @see Client#invoke(Operation)  */ public Void onInvoke(final ObjectInputStream ois, final ObjectOutputStream oos) throws IOException { try { ois.readByte(); throw new IOException("Unexpected data received from the server after close."); } catch (EOFException expected) { // connection successfully closed by the server } return null; } }; /** * The {@link OperationHandler}s by operation. */ private ConcurrentHashMap operationHandlers; /** * The {@link Thread} that will manage accepting {@link Client} connections. *

* When this is null the {@link Server} is not running. */ private SocketThread serverThread; /** * Should the running {@link Server} terminate as soon as possible? */ private AtomicBoolean isTerminating = new AtomicBoolean(false); private ServerSocket serverSocket; /** * A map of {@link ClientConnection} by connection number. */ private ConcurrentHashMap clientConnections; private int serverDirectUsage = 0; /** * Construct a {@link Server} that will accept {@link Client} connections * and requests on the specified port. * * @param port the port on which to accept {@link Client} connections and requests */ public Server(int port) { this.operationHandlers = new ConcurrentHashMap(); this.clientConnections = new ConcurrentHashMap(); } /** * Registers the specified {@link OperationHandler} for an operation. * * @param handler the {@link OperationHandler} */ public void addOperationHandler(OperationHandler handler) { this.operationHandlers.put(handler.getType(), handler); } /** * Opens and starts the {@link Server}. *

* Does nothing if the {@link Server} is already open. * * @return the {@link InetAddress} on which the {@link Server} * is accepting requests from {@link Client}s. * @throws IOException if not able to create ServerSocket */ public synchronized InetAddress open() throws IOException { if (serverThread == null) { if (ALLOW_DIRECT_CALLS) { serverThread = SOCKET_THREADS.poll(); } if (serverThread == null) { serverSocket = createServerSocket(); serverThread = new SocketThread(); serverThread.serverSocket = serverSocket; serverThread.setBoundServer(this); serverThread.start(); } else { serverSocket = serverThread.serverSocket; serverThread.setBoundServer(this); assert !serverThread.wasUsed(); } if (ALLOW_DIRECT_CALLS) { PORT_2_SERVER.put(serverSocket.getLocalPort(), this); } } return getInetAddress(); } /** * Obtains the {@link InetAddress} on which the {@link Server} is listening. * * @return the {@link InetAddress} */ public synchronized InetAddress getInetAddress() { if (serverThread != null) { try { return getServerInetAddress(); } catch (SocketException e) { return serverSocket.getInetAddress(); } catch (UnknownHostException e) { return serverSocket.getInetAddress(); } } else { throw new IllegalStateException("Server is not open"); } } /** * Obtains the port on which the {@link Server} is listening. * * @return the port */ public synchronized int getPort() { if (serverThread != null) { return serverSocket.getLocalPort(); } else { throw new IllegalStateException("Server is not open"); } } public synchronized void clientConnectedDirectly() { serverDirectUsage++; } public synchronized void closeWasCalledOnClient() { if (serverDirectUsage == 0) { throw new IllegalStateException( "customization already closed, class: " + this.getClass().getSimpleName()); } serverDirectUsage--; } /** * Stops the {@link Server}. *

* Does nothing if the {@link Server} is already stopped. */ public synchronized void close() { if (serverThread != null) { PORT_2_SERVER.remove(getPort()); if (ALLOW_DIRECT_CALLS) { if (serverDirectUsage > 0) { throw new IllegalStateException( "The close() method was not called. " + "Customizations implementing Closeable need to be closed. " + "See https://github.com/jsr107/jsr107tck/issues/100" + ", server type " + this.getClass().getSimpleName() ); } } if (!serverThread.wasUsed()) { SOCKET_THREADS.offer(serverThread); } else { //we're now terminating isTerminating.set(true); if (clientConnections.size() > 0) { LOG.warning("Open client connections: " + clientConnections); throw new IllegalStateException( "Excepting no open client connections. " + "Customizations implementing Closeable need to be closed. " + "See https://github.com/jsr107/jsr107tck/issues/100" + ", server type " + this.getClass().getSimpleName() ); } //stop the server socket try { serverSocket.close(); } catch (IOException e) { //failed to close the server socket - but we don't care } serverSocket = null; //interrupt the server thread serverThread.interrupt(); } serverThread = null; //stop the clients for (ClientConnection clientConnection : clientConnections.values()) { clientConnection.close(); } this.clientConnections = new ConcurrentHashMap(); } } /** * Asynchronously handles {@link Client} requests via a {@link Socket} using the * defined {@link OperationHandler}s. */ private static class ClientConnection extends Thread implements AutoCloseable { private Server server; /** * The {@link ClientConnection} identity. */ private int identity; /** * The {@link Socket} to the {@link Client}. */ private Socket socket; /** * Constructs a {@link ClientConnection}. * * @param identity the identity for the {@link ClientConnection} * @param socket the {@link Socket} on which to receive and respond to * {@link Client} requests */ public ClientConnection(Server server, int identity, Socket socket) { this.server = server; this.identity = identity; this.socket = socket; } /** * Obtains the identity for the {@link ClientConnection}. * * @return the identity */ public int getIdentity() { return this.identity; } /** * {@inheritDoc} */ @Override public void run() { try { ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream()); ObjectInputStream ois = new ObjectInputStream(socket.getInputStream()); while (true) { try { String operation = (String) ois.readObject(); if (CLOSE_OPERATION.getType().equals(operation)) { // regular close, remove before closing server.clientConnections.remove(identity); // connection close means we acknowledge to the client and the client may // complete the close operation. socket.close(); socket = null; break; } server.executeOperation(operation, ois, oos); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } catch (IOException e) { //any error closes the connection } finally { if (socket != null) { try { socket.close(); } catch (IOException e) { //failed to close the socket - but we don't care } } //remove this from the server server.clientConnections.remove(identity); } } /** * {@inheritDoc} */ public void close() { try { socket.close(); } catch (IOException e) { //failed to close the socket - but we don't care } finally { socket = null; } } } public void executeOperation(String operation, ObjectInputStream ois, ObjectOutputStream oos) throws ClassNotFoundException, IOException { OperationHandler handler = operationHandlers.get(operation); if (handler != null) { handler.onProcess(ois, oos); } else { throw new IllegalArgumentException("no handler for: " + operation); } } private static InetAddress serverSocketAddress = null; private ServerSocket createServerSocket() throws IOException { final int ephemeralPort = 0; // JW: change from original TCK: always use ephemeral port! ServerSocket result = new ServerSocket(ephemeralPort, 50, getServerInetAddress()); LOG.log(Level.INFO, "Starting " + this.getClass().getCanonicalName() + " server at address:" + getServerInetAddress() + " port:" + result.getLocalPort()); return result; } /** * To support distributed testing, return a non-loopback address if available. *

* By default, on a machine with multiple network interfaces, the appropriate * ip address is selected based on values of java network system properties java.net.preferIPv4Stack * and java.net.preferIPv6Addresses. *

* A user can override the automated selection by specifying a network interface with * system property org.jsr107.tck.support.server.networkinterface set to the * display name of the network interface or explicitly setting the server address with * system property org.jsr107.tck.support.server.address. The address value may be either * ipv4 or ipv6, the value should be consistent with the values of the java network properties q * mentioned above. *

* @return remote addressable inet address * @throws SocketException * @throws UnknownHostException */ private static InetAddress getServerInetAddress() throws SocketException, UnknownHostException { if (serverSocketAddress == null) { boolean preferIPV4Stack = Boolean.getBoolean("java.net.preferIPv4Stack"); boolean preferIPV6Addresses = Boolean.getBoolean("java.net.preferIPv6Addresses") && !preferIPV4Stack; String serverAddress = System.getProperty("org.jsr107.tck.support.server.address"); if (serverAddress != null) { try { serverSocketAddress = InetAddress.getByName(serverAddress); } catch (UnknownHostException e) { // ignore LOG.log(Level.WARNING, "ignoring system property org.jsr107.tck.support.server.address due to exception", e); } } NetworkInterface serverSocketNetworkInterface = null; if (serverSocketAddress == null) { String niName = System.getProperty("org.jsr107.tck.support.server.networkinterface"); try { serverSocketNetworkInterface = niName == null ? null : NetworkInterface.getByName(niName); if (serverSocketNetworkInterface != null) { serverSocketAddress = getFirstNonLoopbackAddress(serverSocketNetworkInterface, preferIPV4Stack, preferIPV6Addresses); } if (serverSocketAddress == null) { LOG.log(Level.WARNING, "ignoring system property org.jsr107.tck.support.server.networkinterface with value:" + niName); } } catch (SocketException e) { LOG.log(Level.WARNING, "ignoring system property org.jsr107.tck.support.server.networkinterface due to exception", e); } } if (serverSocketAddress == null) { serverSocketAddress = getFirstNonLoopbackAddress(preferIPV4Stack, preferIPV6Addresses); } if (serverSocketAddress == null) { LOG.warning("no remote ip address available so only possible to test using loopback address."); serverSocketAddress = InetAddress.getLocalHost(); } } return serverSocketAddress; } /** * Get non-loopback address. InetAddress.getLocalHost() does not work on machines without static ip address. * * @param preferIPv4 true iff require IPv4 addresses only * @param preferIPv6 true iff prefer IPv6 addresses * @return nonLoopback {@link InetAddress} * @throws SocketException */ private static InetAddress getFirstNonLoopbackAddress(boolean preferIPv4, boolean preferIPv6) throws SocketException { InetAddress result = null; Enumeration en = NetworkInterface.getNetworkInterfaces(); while (result == null && en.hasMoreElements()) { result = getFirstNonLoopbackAddress((NetworkInterface) en.nextElement(), preferIPv4, preferIPv6); } return result; } /** * Get non-loopback address. InetAddress.getLocalHost() does not work on machines without static ip address. * * @param ni target network interface * @param preferIPv4 true iff require IPv4 addresses only * @param preferIPv6 true iff prefer IPv6 addresses * @return nonLoopback {@link InetAddress} * @throws SocketException */ private static InetAddress getFirstNonLoopbackAddress(NetworkInterface ni, boolean preferIPv4, boolean preferIPv6) throws SocketException { InetAddress result = null; // skip virtual interface name, PTP and non-running interface. if (ni.isVirtual() || ni.isPointToPoint() || ! ni.isUp()) { return result; } LOG.info("Interface name is: " + ni.getDisplayName()); for (Enumeration en2 = ni.getInetAddresses(); en2.hasMoreElements(); ) { InetAddress addr = (InetAddress) en2.nextElement(); if (!addr.isLoopbackAddress()) { if (addr instanceof Inet4Address) { if (preferIPv6) { continue; } result = addr; break; } if (addr instanceof Inet6Address) { if (preferIPv4) { continue; } result = addr; break; } } } return result; } static AtomicInteger connectionId = new AtomicInteger(); static class SocketThread extends Thread { private AtomicBoolean wasUsed = new AtomicBoolean(); ServerSocket serverSocket; volatile Server boundServer; public void setBoundServer(final Server _boundServer) { boundServer = _boundServer; } public boolean wasUsed() { return wasUsed.get(); } @Override public void run() { try { while (!boundServer.isTerminating.get()) { Socket socket = serverSocket.accept(); wasUsed.set(true); ClientConnection clientConnection = new ClientConnection(boundServer, connectionId.incrementAndGet(), socket); boundServer.clientConnections.put(clientConnection.getIdentity(), clientConnection); clientConnection.start(); } } catch (IOException e) { // ignore } finally { wasUsed.set(true); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy