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

com.skjolberg.mockito.soap.SoapEndpointRule Maven / Gradle / Ivy

package com.skjolberg.mockito.soap;

import java.io.IOException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;

import javax.net.ServerSocketFactory;
import javax.xml.ws.Endpoint;
import javax.xml.ws.spi.Provider;

import org.apache.cxf.Bus;
import org.apache.cxf.endpoint.EndpointException;
import org.apache.cxf.endpoint.ServerImpl;
import org.apache.cxf.jaxws.EndpointImpl;
import org.apache.cxf.jaxws.JaxWsServerFactoryBean;
import org.apache.cxf.jaxws.support.JaxWsServiceFactoryBean;
import org.apache.cxf.service.ServiceImpl;
import org.apache.cxf.service.model.EndpointInfo;
import org.apache.cxf.transport.ChainInitiationObserver;
import org.apache.cxf.transport.Destination;
import org.apache.cxf.transport.DestinationFactory;
import org.apache.cxf.transport.DestinationFactoryManager;
import org.junit.ClassRule;

/**
 * Rule for mocking SOAP services using {@linkplain Endpoint}s. Multiple services can run on the same port.
 * If used as a {@linkplain ClassRule}, the rule can be used to reserve random free ports. 
 * Resulting reserved ports are set as system properties to port names provided by the caller.
 * 
 * @author [email protected]
 *
 */

public class SoapEndpointRule extends SoapServiceRule {
	
	private static final int PORT_RANGE_MAX = 65535;
	private static final int PORT_RANGE_START = 1024+1;
	private static final int PORT_RANGE_END = PORT_RANGE_MAX;

	private class PortReservation {
		public PortReservation(String portName) {
			this.propertyName = portName;
		}
		private final String propertyName;
		private Destination destination;
		private int port = -1;
		
		public void reserved(int port, Destination destination) {
			this.port = port;
			this.destination = destination;
			
			System.setProperty(propertyName, Integer.toString(port));
		}
		
		public void stop() {
			if(destination != null) {
				destination.shutdown();
				
				System.clearProperty(propertyName);
				
				this.port = -1;
			}
		}

		public void start() {
			// systematically try ports in range
			// starting at random offset
			int portRange = portRangeEnd - portRangeStart + 1;
			
			int offset = new Random().nextInt(portRange);

			for(int i = 0; i < portRange; i++) {
				try {
					int candidatePort = portRangeStart + (offset + portRange) % portRange;
					
					if(isPortAvailable(candidatePort)) {
						Destination destination = reservePort(candidatePort); // port might now be taken
						
						reserved(candidatePort, destination);
						
						return;
					}
				} catch(Exception e) {
					// continue
				}
			}
			throw new RuntimeException("Unable to reserve port for " + propertyName);
			
		}

		public Destination getDestination() {
			return destination;
		}

		public int getPort() {
			return port;
		}

		public String getPropertyName() {
			return propertyName;
		}
		
	}   
	
	protected static boolean isPortAvailable(int port) {
		try {
			ServerSocket serverSocket = ServerSocketFactory.getDefault().createServerSocket(port, 1, InetAddress.getByName("localhost"));
			serverSocket.close();
			return true;
		}
		catch (Exception ex) {
			return false;
		}
	}
	
	public static SoapEndpointRule newInstance() {
		return new SoapEndpointRule();
	}
	
	public static SoapEndpointRule newInstance(String ... portNames) {
		return new SoapEndpointRule(portNames);
	}

	public static SoapEndpointRule newInstance(int portRangeStart, int portRangeEnd, String ... portNames) {
		return new SoapEndpointRule(portRangeStart, portRangeEnd, portNames);
	}

	private Map endpoints = new HashMap<>();

	private List reservations = new ArrayList<>();

	private final int portRangeStart;
	private final int portRangeEnd;
	
	public SoapEndpointRule() {
		this(PORT_RANGE_START, PORT_RANGE_END);
	}
	
	public SoapEndpointRule(String ... portNames) {
		this(PORT_RANGE_START, PORT_RANGE_END, portNames);
	}
	
	public SoapEndpointRule(int portRangeStart, int portRangeEnd, String ... portNames) {
		if(portRangeStart <= 0) {
			throw new IllegalArgumentException("Port range start must be greater than 0.");
		}
		if(portRangeEnd < portRangeStart) {
			throw new IllegalArgumentException("Port range end must not be lower than port range end.");
		}
		if(portRangeEnd > PORT_RANGE_MAX) {
			throw new IllegalArgumentException("Port range end must not be larger than " + PORT_RANGE_MAX + ".");
		}
		if(portNames != null && portNames.length > (portRangeEnd - portRangeStart + 1)) {
			throw new IllegalArgumentException("Cannot reserve " + portNames.length + " in range " + portRangeStart + "-" + portRangeEnd + ".");
		}

		this.portRangeStart = portRangeStart;
		this.portRangeEnd = portRangeEnd;
		
		if(portNames != null) {
			for(String portName : portNames) {
				reservations.add(new PortReservation(portName));
			}
		}
	}
	
	/**
	 * Get resvered ports.
	 * 
	 * @return map of portName and port value; > 1 if a port has been reserved, -1 otherwise
	 */
	
	public Map getPorts() {
		HashMap ports = new HashMap<>();
		for (PortReservation portReservation : reservations) {
			ports.put(portReservation.getPropertyName(), portReservation.getPort());
		}
		return ports;
	}
	
	/**
	 * Get a specific reserved port by its portName (as passed to the constructor).
	 * 
	 * @param name port name
	 * @return a port > 1 if a port has been reserved, -1 otherwise
	 */
	
	public int getPort(String name) {
		for (PortReservation portReservation : reservations) {
			if(name.equals(portReservation.getPropertyName())) {
				return portReservation.getPort();
			}
		}
		throw new IllegalArgumentException("No reserved port for '" + name + "'.");
	}
	
	
	/**
	 * Attempt to reserve a port by starting a server. The server 
	 * 
	 * @param port port to reserve
	 * @return destination if succsesful
	 * @throws IOException
	 * @throws EndpointException
	 */

	private Destination reservePort(int port) throws IOException, EndpointException {
		JaxWsServiceFactoryBean jaxWsServiceFactoryBean = new JaxWsServiceFactoryBean();
		
		JaxWsServerFactoryBean serverFactoryBean = new JaxWsServerFactoryBean(jaxWsServiceFactoryBean);
		serverFactoryBean.setAddress("http://localhost:" + port);
		
		Bus bus = serverFactoryBean.getBus();
		DestinationFactory destinationFactory = (DestinationFactory) bus.getExtension(DestinationFactoryManager.class).getDestinationFactoryForUri(serverFactoryBean.getAddress());;
		
		EndpointInfo ei = new EndpointInfo(null, Integer.toString(port));
		ei.setAddress(serverFactoryBean.getAddress());
		
		Destination destination = destinationFactory.getDestination(ei, serverFactoryBean.getBus());
		
		ServiceImpl serviceImpl = new ServiceImpl();
		
		org.apache.cxf.endpoint.Endpoint endpoint = new org.apache.cxf.endpoint.EndpointImpl(bus, serviceImpl, ei);
		destination.setMessageObserver(new ChainInitiationObserver(endpoint , bus));
		return destination;
	}

	public  void proxy(T target, Class port, String address, String wsdlLocation, List schemaLocations, Map properties) {
		if(target == null) {
			throw new IllegalArgumentException("Expected proxy target");
		}
		if(port == null) {
			throw new IllegalArgumentException("Expect port class");
		}
		if(address == null) {
			throw new IllegalArgumentException("Expected address");
		}
		URL url;
		try {
			url = new URL(address);
		} catch (MalformedURLException e) {
			throw new IllegalArgumentException("Expected valid address: " + address, e);
		}
		if(endpoints.containsKey(address)) {
			throw new IllegalArgumentException("Endpoint " + address + " already exists");
		}
		
		T serviceInterface = SoapServiceProxy.newInstance(target);

		Destination destination = getDestination(url.getPort());
		
		EndpointImpl endpoint = (EndpointImpl)Provider.provider().createEndpoint(null, serviceInterface);

		Map map = properties != null ? new HashMap(properties) : new HashMap();

		if(wsdlLocation != null || schemaLocations != null) {
			map.put("schema-validation-enabled", true);
			
			if(wsdlLocation != null) {
				endpoint.setWsdlLocation(wsdlLocation);
			}
			
			if(schemaLocations != null) {
				endpoint.setSchemaLocations(schemaLocations);
			}
		}
		endpoint.setProperties(map);
		
		if(destination != null) {
			ServerImpl server = endpoint.getServer();
			server.setDestination(destination);
		}
		
		endpoint.publish(address);
		
		endpoints.put(address, endpoint);
	}

	private Destination getDestination(int port) {
		for(PortReservation reservation : reservations) {
			if(reservation.getPort() == port) {
				return reservation.getDestination();
			}
		}
		return null;
	}

	protected void before() throws Throwable {
		// reserve ports for all ports 
		for(PortReservation reservation : reservations) {
			reservation.start();
		}
	}

	protected void after() {
		destroy();
	}
	
	/**
	 * Stop and remove endpoints, keeping port reservations.
	 * 
	 */

	public void clear() {
		for (Entry entry : endpoints.entrySet()) {
			entry.getValue().stop();
		}
		endpoints.clear();
	}

	public void destroy() {
		for (Entry entry : endpoints.entrySet()) {
			entry.getValue().getServer().stop();
			entry.getValue().stop();
		}
		for(PortReservation reservation : reservations) {
			reservation.stop();
		}
	}

	public void stop() {
		// stop endpoints
		for (Entry entry : endpoints.entrySet()) {
			entry.getValue().getServer().stop();
		}
	}

	@Override
	public void start() {
		// republish 
		for (Entry entry : endpoints.entrySet()) {
			entry.getValue().getServer().start();
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy