1   package com.trendmicro.grid.acl.ds.msmq;
2   
3   import net.sf.tinyjee.streams.ByteBufferOutputStream;
4   import net.sf.tinyjee.util.FileUtils;
5   import org.w3c.dom.*;
6   
7   import javax.xml.parsers.DocumentBuilder;
8   import javax.xml.parsers.DocumentBuilderFactory;
9   import javax.xml.transform.Transformer;
10  import javax.xml.transform.TransformerException;
11  import javax.xml.transform.TransformerFactory;
12  import javax.xml.transform.dom.DOMSource;
13  import javax.xml.transform.stream.StreamResult;
14  import java.io.IOException;
15  import java.io.OutputStream;
16  import java.net.HttpURLConnection;
17  import java.net.URI;
18  import java.net.URL;
19  import java.text.DateFormat;
20  import java.text.SimpleDateFormat;
21  import java.util.Arrays;
22  import java.util.Date;
23  import java.util.List;
24  import java.util.UUID;
25  
26  /**
27   * This is a simple wrapper for a XML formatted MSMQ SOAP Request used to send message to MSMQ.
28   *
29   * @author juergen_kellerer, werner_watzka, 2010-06-02
30   * @version 1.0
31   */
32  public class MSMQSender implements Sender {
33  
34  	static final long MS_PER_MONTH = 30 * 24 * 60 * 60 * 1000L;
35  
36  	static final UUID sourceQMGuid = UUID.randomUUID(); // specifies the machine ID
37  	static final Document template;
38  	static final DateFormat dateFormatTemplate = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
39  
40  	static {
41  		try {
42  			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
43  			factory.setNamespaceAware(true);
44  			DocumentBuilder builder = factory.newDocumentBuilder();
45  			template = builder.parse(MSMQSender.class.getResourceAsStream("/msmq-soap-send-template.xml"));
46  
47  			// Setting the MachineID
48  			findFirstElement(template.getDocumentElement(), "Header", "Msmq", "SourceQmGuid").setTextContent(sourceQMGuid.toString());
49  		} catch (Exception e) {
50  			throw new RuntimeException(e);
51  		}
52  	}
53  
54  	static Element findFirstElement(Node startNode, String... path) {
55  		NodeList nl = startNode.getChildNodes();
56  
57  		final int length = nl.getLength();
58  		for (int i = 0; i < length; i++) {
59  			final Node node = nl.item(i);
60  
61  			if (!(node instanceof Element))
62  				continue;
63  
64  			final Element el = (Element) node;
65  			if (path.length > 0 && !path[0].equals(el.getLocalName()))
66  				continue;
67  
68  			if (path.length > 1) {
69  				String[] reducedPath = new String[path.length - 1];
70  				System.arraycopy(path, 1, reducedPath, 0, reducedPath.length);
71  				return findFirstElement(el, reducedPath);
72  			}
73  
74  			return el;
75  		}
76  
77  		// Do not return null, fail instead!
78  		throw new IllegalArgumentException("Didn't find the requested element " + Arrays.toString(path) + " inside " + startNode);
79  	}
80  
81  	static String toMSMQPriority(int priority) {
82  		// TODO: Convert the given input prio (1: lowest, 10: highest, 5: normal)
83  		// TODO: to the priority system used by MSMQ.
84  		return String.valueOf(priority);
85  	}
86  
87  	URI endpoint;
88  	String queueName;
89  	DateFormat dateFormat = (DateFormat) dateFormatTemplate.clone();    // NOSONAR - Cloning is safe to use and avoids parsing the
90  	//                                                                               format pattern more than once.
91  
92  	Document message;
93  	Element headerElement, pathElement, propertiesElement, msmqElement;
94  
95  	@Override
96  	public void reset(List<URI> endpoints, String queueName) {
97  		endpoint = endpoints.get(0);
98  		this.queueName = queueName;
99  		message = (Document) template.cloneNode(true);
100 
101 		final Element rootElement = message.getDocumentElement();
102 		headerElement = findFirstElement(rootElement, "Header");
103 		pathElement = findFirstElement(headerElement, "path");
104 		propertiesElement = findFirstElement(headerElement, "properties");
105 		msmqElement = findFirstElement(headerElement, "Msmq");
106 	}
107 
108 	@Override
109 	public void send(int priority, String messageBody) throws IOException {
110 
111 		// TODO: finish / test
112 
113 		URL url = new URL(endpoint.toURL(), queueName);
114 		HttpURLConnection huc = (HttpURLConnection) url.openConnection();
115 		huc.setRequestMethod("POST");
116 		huc.setDoOutput(true);
117 
118 		send(priority, messageBody, huc.getOutputStream());
119 
120 		if (huc.getResponseCode() != HttpURLConnection.HTTP_OK) {
121 			ByteBufferOutputStream bOut = new ByteBufferOutputStream();
122 			FileUtils.copy(huc.getErrorStream(), bOut);
123 
124 			throw new IOException("Failed sending message to " + queueName +
125 					", caused by; HTTP-Response: " + huc.getResponseCode() + " " + huc.getResponseMessage() +
126 					"\nDetails:\n" + bOut.toString());
127 		}
128 	}
129 
130 	void send(int priority, String messageBody, OutputStream out) throws IOException {
131 
132 		// TODO: finish / test
133 
134 		// Set routing.
135 		findFirstElement(pathElement, "to").setTextContent("MSMQ:DIRECT=OS:..???../" + queueName);
136 		findFirstElement(pathElement, "id").setTextContent("uuid:1000@" + UUID.randomUUID());
137 
138 		// Set prio
139 		findFirstElement(msmqElement, "Priority").setTextContent(toMSMQPriority(priority));
140 
141 		// Set dates
142 		long timeOffset = System.currentTimeMillis();
143 		findFirstElement(propertiesElement, "expiresAt").setTextContent(
144 				dateFormat.format(new Date(timeOffset + MS_PER_MONTH)));
145 		findFirstElement(propertiesElement, "sentAt").setTextContent(
146 				dateFormat.format(new Date(timeOffset)));
147 		findFirstElement(msmqElement, "TTrq").setTextContent(
148 				dateFormat.format(new Date(timeOffset)));
149 
150 		// Add the message body
151 		CDATASection cdataSection = message.createCDATASection(messageBody);
152 		findFirstElement(message.getDocumentElement(), "Body").appendChild(cdataSection);
153 
154 		// Serialize the XML to the output stream.
155 		try {
156 			Transformer tf = TransformerFactory.newInstance().newTransformer();
157 			tf.transform(new DOMSource(message), new StreamResult(out));
158 		} catch (TransformerException e) {
159 			throw new IOException(e);
160 		}
161 	}
162 }