/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.cxf.systest.ws.rm;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.logging.Logger;
import javax.jws.WebService;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Endpoint;
import javax.xml.ws.Provider;
import javax.xml.ws.Service.Mode;
import javax.xml.ws.ServiceMode;
import javax.xml.xpath.XPathConstants;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.apache.cxf.Bus;
import org.apache.cxf.BusFactory;
import org.apache.cxf.bus.spring.SpringBusFactory;
import org.apache.cxf.common.logging.LogUtils;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.greeter_control.Greeter;
import org.apache.cxf.greeter_control.GreeterService;
import org.apache.cxf.helpers.XPathUtils;
import org.apache.cxf.staxutils.StaxUtils;
import org.apache.cxf.systest.ws.util.ConnectionHelper;
import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
import org.apache.cxf.ws.rm.MessageCallback;
import org.apache.cxf.ws.rm.RMManager;
import org.apache.cxf.ws.rm.RMMessageConstants;
import org.junit.After;
import org.junit.Test;
/**
* Tests the operation of MessageCallback for one-way messages to the server.
*/
public class MessageCallbackOnewayTest extends AbstractBusClientServerTestBase {
public static final String PORT = allocatePort(MessageCallbackOnewayTest.class);
private static final String GREETER_ADDRESS = "http://localhost:" + PORT + "/SoapContext/GreeterPort";
private static final Long RETRANSMISSION_INTERVAL = new Long(2000);
private static final Logger LOG = LogUtils.getLogger(MessageCallbackOnewayTest.class);
private Bus serverBus;
private Endpoint endpoint;
private Bus greeterBus;
private Greeter greeter;
private RecordingMessageCallback callback;
@After
public void tearDown() throws Exception {
try {
stopClient();
} catch (Throwable t) {
//ignore
}
try {
stopServer();
} catch (Throwable t) {
//ignore
}
Thread.sleep(100);
}
/**
* Checks that all callbacks are received, that messages are accepted in order, and that each message is accepted
* before it is acknowledged (order of acknowledgements doesn't really matter).
*/
private void verifyCallbacks() {
List<Callback> cbs = callback.getCallbacks();
Set<Long> acks = new HashSet<>();
long nextNum = 1;
for (Callback cb: cbs) {
if (cb.isAccept()) {
assertEquals(nextNum++, cb.getMsgNumber());
} else {
assertTrue(cb.getMsgNumber() < nextNum);
Long num = Long.valueOf(cb.getMsgNumber());
assertFalse(acks.contains(num));
acks.add(num);
}
}
}
@Test
public void testAtLeastOnce() throws Exception {
testOnewayAtLeastOnce(null);
}
@Test
public void testAtLeastOnceAsyncExecutor() throws Exception {
testOnewayAtLeastOnce(Executors.newSingleThreadExecutor());
}
private void testOnewayAtLeastOnce(Executor executor) throws Exception {
init("org/apache/cxf/systest/ws/rm/atleastonce.xml", executor);
greeterBus.getOutInterceptors().add(new MessageLossSimulator());
RMManager manager = greeterBus.getExtension(RMManager.class);
manager.getConfiguration().setBaseRetransmissionInterval(RETRANSMISSION_INTERVAL);
String[] callArgs = new String[] {"one", "two", "three", "four"};
for (int i = 0; i < callArgs.length; i++) {
greeter.greetMeOneWay(callArgs[i]);
}
callback.waitDone(8, 3000, 60000);
verifyCallbacks();
}
@Test
public void testAtMostOnce() throws Exception {
testOnewayAtMostOnce(null);
}
@Test
public void testAtMostOnceAsyncExecutor() throws Exception {
testOnewayAtMostOnce(Executors.newSingleThreadExecutor());
}
private void testOnewayAtMostOnce(Executor executor) throws Exception {
init("org/apache/cxf/systest/ws/rm/atmostonce.xml", executor);
greeterBus.getOutInterceptors().add(new MessageLossSimulator());
RMManager manager = greeterBus.getExtension(RMManager.class);
manager.getConfiguration().setBaseRetransmissionInterval(RETRANSMISSION_INTERVAL);
String[] callArgs = new String[] {"one", "two", "three", "four"};
for (int i = 0; i < callArgs.length; i++) {
greeter.greetMeOneWay(callArgs[i]);
}
callback.waitDone(8, 3000, 60000);
verifyCallbacks();
}
@Test
public void testExactlyOnce() throws Exception {
testOnewayExactlyOnce(null);
}
@Test
public void testExactlyOnceAsyncExecutor() throws Exception {
testOnewayExactlyOnce(Executors.newSingleThreadExecutor());
}
private void testOnewayExactlyOnce(Executor executor) throws Exception {
init("org/apache/cxf/systest/ws/rm/exactlyonce.xml", executor);
greeterBus.getOutInterceptors().add(new MessageLossSimulator());
RMManager manager = greeterBus.getExtension(RMManager.class);
manager.getConfiguration().setBaseRetransmissionInterval(RETRANSMISSION_INTERVAL);
String[] callArgs = new String[] {"one", "two", "three", "four"};
for (int i = 0; i < callArgs.length; i++) {
greeter.greetMeOneWay(callArgs[i]);
}
callback.waitDone(8, 3000, 60000);
verifyCallbacks();
}
@Test
public void testExactlyOnceInOrder() throws Exception {
testOnewayExactlyOnceInOrder(null);
}
@Test
public void testExactlyOnceInOrderAsyncExecutor() throws Exception {
testOnewayExactlyOnceInOrder(Executors.newSingleThreadExecutor());
}
private void testOnewayExactlyOnceInOrder(Executor executor) throws Exception {
init("org/apache/cxf/systest/ws/rm/exactlyonce-inorder.xml", executor);
greeterBus.getOutInterceptors().add(new MessageLossSimulator());
RMManager manager = greeterBus.getExtension(RMManager.class);
manager.getConfiguration().setBaseRetransmissionInterval(RETRANSMISSION_INTERVAL);
String[] callArgs = new String[] {"one", "two", "three", "four"};
for (int i = 0; i < callArgs.length; i++) {
greeter.greetMeOneWay(callArgs[i]);
}
callback.waitDone(8, 3000, 60000);
verifyCallbacks();
}
// --- test utilities ---
private void init(String cfgResource, Executor executor) {
SpringBusFactory bf = new SpringBusFactory();
initServer(bf, cfgResource);
initGreeterBus(bf, cfgResource);
initProxy(executor);
}
private void initServer(SpringBusFactory bf, String cfgResource) {
String derbyHome = System.getProperty("derby.system.home");
try {
synchronized (GreeterProvider.CALL_ARGS) {
GreeterProvider.CALL_ARGS.clear();
}
System.setProperty("derby.system.home", derbyHome + "-server");
serverBus = bf.createBus(cfgResource);
BusFactory.setDefaultBus(serverBus);
LOG.info("Initialised bus " + serverBus + " with cfg file resource: " + cfgResource);
LOG.info("serverBus inInterceptors: " + serverBus.getInInterceptors());
endpoint = Endpoint.publish(GREETER_ADDRESS, new GreeterProvider());
} finally {
if (derbyHome != null) {
System.setProperty("derby.system.home", derbyHome);
} else {
System.clearProperty("derby.system.home");
}
}
}
private void initGreeterBus(SpringBusFactory bf,
String cfgResource) {
greeterBus = bf.createBus(cfgResource);
BusFactory.setDefaultBus(greeterBus);
LOG.fine("Initialised greeter bus with configuration: " + cfgResource);
}
private void initProxy(Executor executor) {
GreeterService gs = new GreeterService();
if (null != executor) {
gs.setExecutor(executor);
}
greeter = gs.getGreeterPort();
try {
updateAddressPort(greeter, PORT);
} catch (Exception e) {
//ignore
}
LOG.fine("Created greeter client.");
ConnectionHelper.setKeepAliveConnection(greeter, false);
callback = new RecordingMessageCallback();
((BindingProvider)greeter).getRequestContext().put(RMMessageConstants.RM_CLIENT_CALLBACK, callback);
}
private void stopClient() {
if (null != greeterBus) {
//ensure we close the decoupled destination of the conduit,
//so that release the port if the destination reference count hit zero
if (greeter != null) {
ClientProxy.getClient(greeter).getConduit().close();
}
greeterBus.shutdown(true);
greeter = null;
greeterBus = null;
}
}
private void stopServer() {
if (null != endpoint) {
LOG.info("Stopping Greeter endpoint");
endpoint.stop();
} else {
LOG.info("No endpoint active.");
}
endpoint = null;
if (null != serverBus) {
serverBus.shutdown(true);
serverBus = null;
}
}
@WebService(serviceName = "GreeterService",
portName = "GreeterPort",
targetNamespace = "http://cxf.apache.org/greeter_control",
wsdlLocation = "/wsdl/greeter_control.wsdl")
@ServiceMode(Mode.PAYLOAD)
public static class GreeterProvider implements Provider<Source> {
public static final List<String> CALL_ARGS = new ArrayList<>();
public Source invoke(Source obj) {
Node el;
try {
el = StaxUtils.read(obj);
} catch (Exception e) {
throw new RuntimeException(e);
}
if (el instanceof Document) {
el = ((Document)el).getDocumentElement();
}
Map<String, String> ns = new HashMap<>();
ns.put("ns", "http://cxf.apache.org/greeter_control/types");
XPathUtils xp = new XPathUtils(ns);
String s = (String)xp.getValue("/ns:greetMe/ns:requestType",
el,
XPathConstants.STRING);
if (s == null || "".equals(s)) {
s = (String)xp.getValue("/ns:greetMeOneWay/ns:requestType",
el,
XPathConstants.STRING);
synchronized (CALL_ARGS) {
CALL_ARGS.add(s);
}
return null;
} else {
synchronized (CALL_ARGS) {
CALL_ARGS.add(s);
}
String resp =
"<greetMeResponse "
+ "xmlns=\"http://cxf.apache.org/greeter_control/types\">"
+ "<responseType>" + s.toUpperCase() + "</responseType>"
+ "</greetMeResponse>";
return new StreamSource(new StringReader(resp));
}
}
}
private static class RecordingMessageCallback implements MessageCallback {
private List<Callback> callbacks = new ArrayList<>();
@Override
public void messageAccepted(String seqId, long msgNum) {
synchronized (callbacks) {
callbacks.add(new Callback(true, msgNum));
callbacks.notifyAll();
}
}
@Override
public void messageAcknowledged(String seqId, long msgNum) {
synchronized (callbacks) {
callbacks.add(new Callback(false, msgNum));
callbacks.notifyAll();
}
}
public List<Callback> getCallbacks() {
return callbacks;
}
/**
* Wait for expected number of callbacks.
*
* @param count expected number of callbacks
* @param delay extra time to wait after expected number received (in case more are coming)
* @param timeout maximum time to wait, in milliseconds
*/
public void waitDone(int count, int delay, long timeout) {
long start = System.currentTimeMillis();
synchronized (callbacks) {
while (callbacks.size() < count) {
long remain = start + timeout - System.currentTimeMillis();
if (remain <= 0) {
fail("Expected " + count + " callbacks, only got " + callbacks.size());
}
try {
callbacks.wait(remain);
} catch (InterruptedException e) { /* ignored */ }
}
try {
callbacks.wait((long)delay);
} catch (InterruptedException e) { /* ignored */ }
if (callbacks.size() > count) {
fail("Expected " + count + " callbacks, got " + callbacks.size());
}
}
}
}
private static class Callback {
private final boolean accept;
private final long msgNumber;
Callback(boolean acc, long msgNum) {
accept = acc;
msgNumber = msgNum;
}
public boolean isAccept() {
return accept;
}
public long getMsgNumber() {
return msgNumber;
}
}
}