/******************************************************************************* * Copyright (c) 2009 MATERNA Information & Communications. All rights reserved. * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html. For further * project-related information visit http://www.ws4d.org. The most recent * version of the JMEDS framework can be obtained from * http://sourceforge.net/projects/ws4d-javame. ******************************************************************************/ package org.ws4d.java.communication.protocol.soap; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import org.ws4d.java.DPWSFramework; import org.ws4d.java.communication.ProtocolData; import org.ws4d.java.communication.connection.ip.IPAddress; import org.ws4d.java.communication.connection.udp.Datagram; import org.ws4d.java.communication.connection.udp.DatagramInputStream; import org.ws4d.java.communication.connection.udp.UDPClient; import org.ws4d.java.communication.connection.udp.UDPDatagramHandler; import org.ws4d.java.communication.monitor.MonitorStreamFactory; import org.ws4d.java.communication.monitor.MonitoredInputStream; import org.ws4d.java.communication.monitor.MonitoredMessageReceiver; import org.ws4d.java.communication.monitor.MonitoredOutputStream; import org.ws4d.java.communication.monitor.MonitoringContext; import org.ws4d.java.communication.protocol.soap.generator.DefaultMessageDiscarder; import org.ws4d.java.communication.protocol.soap.generator.MessageReceiver; import org.ws4d.java.communication.protocol.soap.generator.SOAPMessageGeneratorFactory; import org.ws4d.java.message.Message; import org.ws4d.java.message.SOAPHeader; import org.ws4d.java.structures.HashMap; import org.ws4d.java.structures.MessageIdBuffer; import org.ws4d.java.types.ByteArrayBuffer; import org.ws4d.java.util.Math; /** * A SOAP-over-UDP client, which allows the sending of a SOAP message as a UDP * datagram packet. */ public class SOAPoverUDPClient { /* * This <code>Announcer</code> implements the "Retransmission" described in * the SOAP-over-UDP (3.4) document with the restriction made by DPWS * Committee Draft 03 (Appendix B). */ public static final int MULTICAST_UNICAST_UDP_REPEAT = 1; // DPWS public static final int UDP_MIN_DELAY = 50; public static final int UDP_MAX_DELAY = 250; public static final int UDP_UPPER_DELAY = 450; // DPWS /** * The internal UDP client. */ private UDPClient client = null; /** * Table of SOAP-over-UDP clients. */ private static HashMap clients = new HashMap(); /** * Returns a SOAP-over-UDP client with address and port for incoming UDP * messages. If no client exists, a new client will be created. * * @param address the local address. * @param port the port. * @return the SOAP-over-UDP client. */ public synchronized static SOAPoverUDPClient get(IPAddress ipAddress, int port, String ifaceName) { if (port == 0) { return new SOAPoverUDPClient(UDPClient.get(ipAddress, port, ifaceName)); } String key = ipAddress.getAddress() + "@" + port + "%" + ifaceName; SOAPoverUDPClient soapc = (SOAPoverUDPClient) clients.get(key); if (soapc != null) return soapc; soapc = new SOAPoverUDPClient(UDPClient.get(ipAddress, port, ifaceName)); clients.put(key, soapc); return soapc; } private SOAPoverUDPClient(UDPClient client) { this.client = client; } /** * Creates a UDP datagram socket and uses this socket to send the given SOAP * message. * <p> * The SOAP message will be sent twice as described in the DPWS 1.1 * specification. * </p> * * @param dstAddress destination address of the SOAP message. * @param dstPort destination port of the SOAP message. * @param ifaceName * @param message SOAP message to send. * @param handler this handler will handle the incoming UDP datagram * packets. * @throws IOException */ public void send(IPAddress dstAddress, int dstPort, Message message, SOAPoverUDPHandler handler, ProtocolData protocolData) throws IOException { if (client.isClosed()) return; ByteArrayBuffer b = SOAPMessageGeneratorFactory.getInstance().getMessage2SOAPGeneratorForCurrentThread().generateSOAPMessage(message, protocolData); boolean putToCache = client.getPort() == 0; sendInternal(dstAddress, dstPort, message, handler, protocolData, b); if (putToCache) { String key = client.getIPAddress().getAddress() + "@" + client.getPort() + "%" + client.getIfaceName(); synchronized (this.getClass()) { clients.put(key, this); } } int repeatCount = SOAPoverUDPClient.MULTICAST_UNICAST_UDP_REPEAT; if (repeatCount <= 0) { return; } int delay = Math.nextInt(SOAPoverUDPClient.UDP_MIN_DELAY, SOAPoverUDPClient.UDP_MAX_DELAY); while (true) { try { Thread.sleep(delay); } catch (InterruptedException e) { // ignore } sendInternal(dstAddress, dstPort, message, handler, protocolData, b); if (--repeatCount == 0) { break; } delay *= 2; if (delay > SOAPoverUDPClient.UDP_UPPER_DELAY) { delay = SOAPoverUDPClient.UDP_UPPER_DELAY; } } } private void sendInternal(IPAddress dstAddress, int dstPort, Message message, SOAPoverUDPHandler handler, ProtocolData protocolData, ByteArrayBuffer b) throws IOException { MonitorStreamFactory monFac = DPWSFramework.getMonitorStreamFactory(); MonitoringContext context = null; try { if (monFac != null) { OutputStream o = new ByteArrayOutputStream(b.getContentLength()); o = new MonitoredOutputStream(o, protocolData); context = monFac.getNewMonitoringContextOut(protocolData); o.write(b.getBuffer(), 0, b.getContentLength()); o.flush(); o.close(); } client.send(dstAddress, dstPort, b.getBuffer(), b.getContentLength(), handler, protocolData); } catch (IOException e) { if (monFac != null) { monFac.sendFault(protocolData, context, e); } throw e; } if (monFac != null) { monFac.send(protocolData, context, message); } } /** * Closes the SOAP-over-UDP client. * <p> * No UDP datagram packets can be sent. * </p> * * @throws IOException */ public synchronized void close() throws IOException { client.close(); } /** * Returns <code>true</code> if the underlying UDP client is closed and * cannot be used for a request, or <code>false</code> if the client can * still be used. * * @return <code>true</code> if the underlying UDP client is closed and * cannot be used for a request, or <code>false</code> if the client * can still be used. */ public synchronized boolean isClosed() { return client.isClosed(); } /** * UDP datagram handler implementation for SOAP messages. */ public static class SOAPoverUDPHandler implements UDPDatagramHandler { private final MessageReceiver receiver; private final DefaultMessageDiscarder discarder; public SOAPoverUDPHandler(MessageReceiver receiver) { super(); this.receiver = receiver; this.discarder = new DuplicateMessageDiscarder(); } /* * (non-Javadoc) * @see * org.ws4d.java.communication.connection.udp.UDPDatagramHandler#handle * (org.ws4d.java.communication.connection.udp.Datagram, * org.ws4d.java.communication.DPWSProtocolData) */ public void handle(Datagram datagram, ProtocolData protocolData) throws IOException { InputStream in = null; MonitorStreamFactory monFac = DPWSFramework.getMonitorStreamFactory(); if (monFac != null) { in = new MonitoredInputStream(new DatagramInputStream(datagram), protocolData); } else { in = new DatagramInputStream(datagram); } final MessageReceiver r; if (monFac != null) { MonitoringContext context = monFac.getNewMonitoringContextIn(protocolData); r = new MonitoredMessageReceiver(receiver, context); } else { r = receiver; } SOAPMessageGeneratorFactory.getInstance().getSOAP2MessageGeneratorForCurrentThread().deliverMessage(in, r, protocolData, discarder); in.close(); } } public static class DuplicateMessageDiscarder extends DefaultMessageDiscarder { private final MessageIdBuffer relMessages = new MessageIdBuffer(); public int discardMessage(SOAPHeader header, ProtocolData protocolData) { int superResult = super.discardMessage(header, protocolData); if (superResult != NOT_DISCARDED) return superResult; if (relMessages.containsOrEnqueue(header.getMessageId())) { return DUPLICATE_MESSAGE; } return NOT_DISCARDED; } } }