/* | |
* 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.camel.component.cm; | |
import java.io.BufferedReader; | |
import java.io.ByteArrayOutputStream; | |
import java.io.IOException; | |
import java.io.InputStreamReader; | |
import java.nio.charset.Charset; | |
import java.util.UUID; | |
import javax.xml.XMLConstants; | |
import javax.xml.parsers.DocumentBuilder; | |
import javax.xml.parsers.DocumentBuilderFactory; | |
import javax.xml.parsers.ParserConfigurationException; | |
import javax.xml.transform.OutputKeys; | |
import javax.xml.transform.Result; | |
import javax.xml.transform.Source; | |
import javax.xml.transform.Transformer; | |
import javax.xml.transform.TransformerException; | |
import javax.xml.transform.TransformerFactory; | |
import javax.xml.transform.dom.DOMSource; | |
import javax.xml.transform.stream.StreamResult; | |
import org.w3c.dom.DOMImplementation; | |
import org.w3c.dom.Document; | |
import org.w3c.dom.Element; | |
import org.w3c.dom.Text; | |
import org.apache.camel.component.cm.exceptions.CMDirectException; | |
import org.apache.camel.component.cm.exceptions.XMLConstructionException; | |
import org.apache.camel.component.cm.exceptions.cmresponse.CMResponseException; | |
import org.apache.camel.component.cm.exceptions.cmresponse.InsufficientBalanceException; | |
import org.apache.camel.component.cm.exceptions.cmresponse.InvalidProductTokenException; | |
import org.apache.camel.component.cm.exceptions.cmresponse.NoAccountFoundForProductTokenException; | |
import org.apache.camel.component.cm.exceptions.cmresponse.UnknownErrorException; | |
import org.apache.camel.component.cm.exceptions.cmresponse.UnroutableMessageException; | |
import org.apache.http.HttpResponse; | |
import org.apache.http.client.HttpClient; | |
import org.apache.http.client.methods.HttpPost; | |
import org.apache.http.entity.StringEntity; | |
import org.apache.http.impl.client.HttpClientBuilder; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
public class CMSenderOneMessageImpl implements CMSender { | |
private static final Logger LOG = LoggerFactory.getLogger(CMSenderOneMessageImpl.class); | |
private final String url; | |
private final UUID productToken; | |
public CMSenderOneMessageImpl(final String url, final UUID productToken) { | |
this.url = url; | |
this.productToken = productToken; | |
} | |
/** | |
* Sends a message to CM endpoints. 1. CMMessage instance is going to be marshalled to xml. 2. Post request xml string to CMEndpoint. | |
*/ | |
@Override | |
public void send(final CMMessage cmMessage) { | |
// See: Check https://dashboard.onlinesmsgateway.com/docs for responses | |
// 1.Construct XML. Throws XMLConstructionException | |
final String xml = createXml(cmMessage); | |
// 2. Try to send to CM SMS Provider ...Throws CMResponseException | |
doHttpPost(url, xml); | |
} | |
private String createXml(final CMMessage message) { | |
try { | |
final ByteArrayOutputStream xml = new ByteArrayOutputStream(); | |
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); | |
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); | |
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); | |
factory.setNamespaceAware(true); | |
// Get the DocumentBuilder | |
final DocumentBuilder docBuilder = factory.newDocumentBuilder(); | |
// Create blank DOM Document | |
final DOMImplementation impl = docBuilder.getDOMImplementation(); | |
final Document doc = impl.createDocument(null, "MESSAGES", null); | |
// ROOT Element es MESSAGES | |
final Element root = doc.getDocumentElement(); | |
// AUTHENTICATION element | |
final Element authenticationElement = doc.createElement("AUTHENTICATION"); | |
final Element productTokenElement = doc.createElement("PRODUCTTOKEN"); | |
authenticationElement.appendChild(productTokenElement); | |
final Text productTokenValue = doc.createTextNode("" + productToken); | |
productTokenElement.appendChild(productTokenValue); | |
root.appendChild(authenticationElement); | |
// MSG Element | |
final Element msgElement = doc.createElement("MSG"); | |
root.appendChild(msgElement); | |
// <FROM>VALUE</FROM> | |
final Element fromElement = doc.createElement("FROM"); | |
fromElement.appendChild(doc.createTextNode(message.getSender())); | |
msgElement.appendChild(fromElement); | |
// <BODY>VALUE</BODY> | |
final Element bodyElement = doc.createElement("BODY"); | |
bodyElement.appendChild(doc.createTextNode(message.getMessage())); | |
msgElement.appendChild(bodyElement); | |
// <TO>VALUE</TO> | |
final Element toElement = doc.createElement("TO"); | |
toElement.appendChild(doc.createTextNode(message.getPhoneNumber())); | |
msgElement.appendChild(toElement); | |
// <DCS>VALUE</DCS> - if UNICODE - messageOut.isGSM338Enc | |
// false | |
if (message.isUnicode()) { | |
final Element dcsElement = doc.createElement("DCS"); | |
dcsElement.appendChild(doc.createTextNode("8")); | |
msgElement.appendChild(dcsElement); | |
} | |
// <REFERENCE>VALUE</REFERENCE> -Alfanum | |
final String id = message.getIdAsString(); | |
if (id != null && !id.isEmpty()) { | |
final Element refElement = doc.createElement("REFERENCE"); | |
refElement.appendChild(doc.createTextNode("" + message.getIdAsString())); | |
msgElement.appendChild(refElement); | |
} | |
// <MINIMUMNUMBEROFMESSAGEPARTS>1</MINIMUMNUMBEROFMESSAGEPARTS> | |
// <MAXIMUMNUMBEROFMESSAGEPARTS>8</MAXIMUMNUMBEROFMESSAGEPARTS> | |
if (message.isMultipart()) { | |
final Element minMessagePartsElement = doc.createElement("MINIMUMNUMBEROFMESSAGEPARTS"); | |
minMessagePartsElement.appendChild(doc.createTextNode("1")); | |
msgElement.appendChild(minMessagePartsElement); | |
final Element maxMessagePartsElement = doc.createElement("MAXIMUMNUMBEROFMESSAGEPARTS"); | |
maxMessagePartsElement.appendChild(doc.createTextNode(Integer.toString(message.getMultiparts()))); | |
msgElement.appendChild(maxMessagePartsElement); | |
} | |
// Creatate XML as String | |
TransformerFactory transformerFactory = TransformerFactory.newInstance(); | |
transformerFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE); | |
final Transformer aTransformer = transformerFactory.newTransformer(); | |
aTransformer.setOutputProperty(OutputKeys.INDENT, "yes"); | |
final Source src = new DOMSource(doc); | |
final Result dest = new StreamResult(xml); | |
aTransformer.transform(src, dest); | |
return xml.toString(); | |
} catch (final TransformerException e) { | |
throw new XMLConstructionException(String.format("Cant serialize CMMessage %s", message), e); | |
} catch (final ParserConfigurationException e) { | |
throw new XMLConstructionException(String.format("Cant serialize CMMessage %s", message), e); | |
} | |
} | |
private void doHttpPost(final String urlString, final String requestString) { | |
final HttpClient client = HttpClientBuilder.create().build(); | |
final HttpPost post = new HttpPost(urlString); | |
post.setEntity(new StringEntity(requestString, Charset.forName("UTF-8"))); | |
try { | |
final HttpResponse response = client.execute(post); | |
final int statusCode = response.getStatusLine().getStatusCode(); | |
LOG.debug("Response Code : {}", statusCode); | |
if (statusCode == 400) { | |
throw new CMDirectException("CM Component and CM API show some kind of inconsistency. " | |
+ "CM is complaining about not using a post method for the request. And this component only uses POST requests. What happens?"); | |
} | |
if (statusCode != 200) { | |
throw new CMDirectException("CM Component and CM API show some kind of inconsistency. The component expects the status code to be 200 or 400. New api released? "); | |
} | |
// So we have 200 status code... | |
// The response type is 'text/plain' and contains the actual | |
// result of the request processing. | |
// We obtaing the result text | |
final BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent())); | |
final StringBuffer result = new StringBuffer(); | |
String line = null; | |
while ((line = rd.readLine()) != null) { | |
result.append(line); | |
} | |
// ... and process it | |
line = result.toString(); | |
if (!line.isEmpty()) { | |
// Line is not empty = error | |
LOG.debug("Result of the request processing: FAILED\n{}", line); | |
// The response text contains the error description. We will | |
// throw a custom exception for each. | |
if (line.contains(CMConstants.ERROR_UNKNOWN)) { | |
throw new UnknownErrorException(); | |
} else if (line.contains(CMConstants.ERROR_NO_ACCOUNT)) { | |
throw new NoAccountFoundForProductTokenException(); | |
} else if (line.contains(CMConstants.ERROR_INSUFICIENT_BALANCE)) { | |
throw new InsufficientBalanceException(); | |
} else if (line.contains(CMConstants.ERROR_UNROUTABLE_MESSAGE)) { | |
throw new UnroutableMessageException(); | |
} else if (line.contains(CMConstants.ERROR_INVALID_PRODUCT_TOKEN)) { | |
throw new InvalidProductTokenException(); | |
} else { | |
// SO FAR i would expect other kind of ERROR. | |
// MSISDN correctness and message validity is client | |
// responsibility | |
throw new CMResponseException("CHECK ME. I am not expecting this. "); | |
} | |
} | |
// Ok. Line is EMPTY - successfully submitted | |
LOG.debug("Result of the request processing: Successfully submited"); | |
} catch (final IOException io) { | |
throw new CMDirectException(io); | |
} catch (Throwable t) { | |
if (!(t instanceof CMDirectException)) { | |
// Chain it | |
t = new CMDirectException(t); | |
} | |
throw (CMDirectException) t; | |
} | |
} | |
} |