package com.rayo.server.servlet;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import org.apache.xerces.dom.CoreDocumentImpl;
import org.dom4j.DocumentException;
import org.dom4j.Namespace;
import org.dom4j.QName;
import org.dom4j.dom.DOMDocument;
import org.dom4j.dom.DOMDocumentFactory;
import org.dom4j.dom.DOMElement;
import org.dom4j.io.DOMReader;
import org.dom4j.io.DOMWriter;
import org.w3c.dom.Element;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSSerializer;
import com.rayo.server.admin.AdminService;
import com.rayo.server.exception.ErrorMapping;
import com.rayo.server.exception.ExceptionMapper;
import com.rayo.server.listener.AdminListener;
import com.rayo.server.util.DomUtils;
import com.voxeo.logging.Loggerf;
import com.voxeo.servlet.xmpp.IQRequest;
import com.voxeo.servlet.xmpp.IQResponse;
import com.voxeo.servlet.xmpp.JID;
import com.voxeo.servlet.xmpp.PresenceMessage;
import com.voxeo.servlet.xmpp.StanzaError;
import com.voxeo.servlet.xmpp.StanzaError.Condition;
import com.voxeo.servlet.xmpp.StanzaError.Type;
import com.voxeo.servlet.xmpp.XmppFactory;
import com.voxeo.servlet.xmpp.XmppServlet;
import com.voxeo.servlet.xmpp.XmppSession;
@SuppressWarnings("serial")
public abstract class AbstractRayoServlet extends XmppServlet implements AdminListener {
private static final Loggerf WIRE = Loggerf.getLogger("com.tropo.ozone.wire");
private static final QName SESSION_QNAME = new QName("session", new Namespace("", "urn:ietf:params:xml:ns:xmpp-session"));
private static final QName BIND_QNAME = new QName("bind", new Namespace("", "urn:ietf:params:xml:ns:xmpp-bind"));
private static final QName PING_QNAME = new QName("ping", new Namespace("", "urn:xmpp:ping"));
private static final String LOCAL_DOMAIN = "local-domain";
private XmppFactory xmppFactory;
private AdminService adminService;
private ExceptionMapper exceptionMapper;
private String localDomain;
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
xmppFactory = (XmppFactory) config.getServletContext().getAttribute(XMPP_FACTORY);
localDomain = config.getInitParameter(LOCAL_DOMAIN);
if (localDomain == null) {
try {
localDomain = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
getLog().warn(e.getMessage());
localDomain = "localhost";
}
}
adminService.readConfigurationFromContext(getServletConfig());
adminService.addAdminListener(this);
}
@Override
public void onPropertyChanged(String property, String newValue) {}
/**
* Called by Spring on component initialization
*
* Cannot be called 'init' since it would override super.init() and ultimately be
* called twice: once by Spring and once by super.init(context)
*/
public void start() {
getLog().info("Initializing %s. Build number: %s", adminService.getServerName(), adminService.getBuildNumber());
}
@Override
protected void doIQRequest(IQRequest request) throws ServletException,IOException {
if (getWireLogger().isDebugEnabled()) {
getWireLogger().debug("%s :: %s", request,
request.getSession().getId());
}
DOMElement requestElement = null;
try {
requestElement = toDOM(request.getElement());
}
catch (DocumentException ee) {
throw new IOException("Could not parse XML content", ee);
}
try {
// Extract Request
DOMElement payload = (DOMElement) requestElement.elementIterator().next();
QName qname = payload.getQName();
if (request.getSession().getType() == XmppSession.Type.INBOUNDCLIENT) {
// Resource Binding
if (qname.equals(BIND_QNAME)) {
String boundJid = request.getFrom().getNode() + "@" + request.getFrom().getDomain() + "/" + request.getFrom().getResource();
DOMElement bindElement = (DOMElement) DOMDocumentFactory.getInstance().createElement(BIND_QNAME);
bindElement.addElement("jid").setText(boundJid);
sendIqResult(request, bindElement);
getLog().info("Bound client resource [jid=%s]", boundJid);
return;
}
if (qname.equals(SESSION_QNAME)) {
sendIqResult(request);
return;
}
}
if (qname.equals(PING_QNAME) || qname.equals(SESSION_QNAME)) {
sendIqResult(request);
return;
}
if (DomUtils.isSupportedNamespace(payload)) {
// Validate jid
if (!validJid(request.getTo())) {
sendIqError(request, StanzaError.Type.CANCEL, StanzaError.Condition.JID_MALFORMED, String.format("Malformed JID", request.getTo()));
} else {
processIQRequest(request, payload);
}
} else {
// We don't handle this type of request...
sendIqError(request, StanzaError.Type.CANCEL, StanzaError.Condition.FEATURE_NOT_IMPLEMENTED, "Feature not supported");
}
} catch (Exception e) {
getLog().error(e.getMessage(),e);
getLog().error("Exception processing IQ request", e);
sendIqError(request, StanzaError.Type.CANCEL, StanzaError.Condition.INTERNAL_SERVER_ERROR, e.getMessage());
}
}
protected abstract void processIQRequest(IQRequest request, DOMElement payload);
@Override
public void onQuiesceModeEntered() {
}
@Override
public void onQuiesceModeExited() {
}
@Override
public void onShutdown() {
}
protected void sendIqError(IQRequest request, String type, String error, String text) throws IOException {
//TODO: Not needed once https://evolution.voxeo.com/ticket/1520421 is fixed
error = error.replaceAll("-", "_");
sendIqError(request, request.createError(StanzaError.Type.valueOf(type.toUpperCase()), StanzaError.Condition.valueOf(error.toUpperCase()), text));
}
protected void sendIqError(IQRequest request, StanzaError.Type type, StanzaError.Condition error, String text) throws IOException {
sendIqError(request, request.createError(type, error, text));
}
protected void sendIqError(IQRequest request, IQResponse response) throws IOException {
response.setFrom(request.getTo());
response.send();
}
protected IQResponse sendIqResult(IQRequest request) throws IOException {
return sendIqResult(request, null);
}
protected IQResponse sendIqResult(IQRequest request, org.w3c.dom.Element result) throws IOException {
IQResponse response = null;
if (result != null) {
response = request.createResult(result);
} else {
response = request.createResult();
}
response.setFrom(request.getTo());
response.send();
return response;
}
protected void sendPresenceError(JID fromJid, JID toJid) throws IOException, ServletException {
sendPresenceError(fromJid, toJid, new Element[]{});
}
protected void sendPresenceError(JID fromJid, JID toJid, Condition condition) throws IOException, ServletException {
sendPresenceError(fromJid, toJid, condition, Type.CANCEL);
}
protected void sendPresenceError(JID fromJid, JID toJid, Condition condition, Type type) throws IOException, ServletException {
sendPresenceError(fromJid, toJid, condition, Type.CANCEL, null);
}
protected void sendPresenceError(JID fromJid, JID toJid, String condition, String type, String text) throws IOException, ServletException {
Condition errorCondition = null;
Type errorType = null;
try {
errorCondition = Condition.valueOf(condition);
errorType = Type.valueOf(type);
} catch (Exception e) {
getLog().error("Cannot parser condition and type: [%s] :: [%s]. Ignoring it.", condition, type);
sendPresenceError(fromJid, toJid);
return;
}
sendPresenceError(fromJid, toJid, errorCondition, errorType, text);
}
protected void sendPresenceError(JID fromJid, JID toJid, Condition condition, Type type, String text) throws IOException, ServletException {
CoreDocumentImpl document = new CoreDocumentImpl(false);
org.w3c.dom.Element errorElement = document.createElement("error");
errorElement.setAttribute("type", type.toString());
org.w3c.dom.Element conditionElement = document.createElement(condition.toString());
errorElement.appendChild(conditionElement);
if (text != null) {
org.w3c.dom.Element textElement = document.createElement("text");
textElement.setTextContent(text);
errorElement.appendChild(textElement);
}
sendPresenceError(fromJid, toJid, errorElement);
}
protected void sendPresenceError(JID fromJid, JID toJid, Element... elements) throws IOException, ServletException {
PresenceMessage errorPresence;
if (elements == null || elements.length == 0) {
errorPresence = getXmppFactory()
.createPresence(fromJid, toJid, "error");
} else {
errorPresence = getXmppFactory()
.createPresence(fromJid, toJid, "error", elements);
}
errorPresence.send();
if (getWireLogger().isDebugEnabled()) {
getWireLogger().debug("%s :: %s",
errorPresence,
errorPresence.getSession().getId());
}
}
protected static String asXML (org.w3c.dom.Element element) {
DOMImplementationLS impl = (DOMImplementationLS)element.getOwnerDocument().getImplementation();
LSSerializer serializer = impl.createLSSerializer();
serializer.getDomConfig().setParameter("xml-declaration", false);
return serializer.writeToString(element);
}
public static DOMElement toDOM (org.dom4j.Element dom4jElement) throws DocumentException {
DOMElement domElement = null;
if (dom4jElement instanceof DOMElement) {
domElement = (DOMElement) dom4jElement;
} else {
DOMDocument requestDocument = (DOMDocument)
new DOMWriter().write(dom4jElement.getDocument());
domElement = (DOMElement)requestDocument.getDocumentElement();
}
return domElement;
}
public static DOMElement toDOM (org.w3c.dom.Element w3cElement) throws DocumentException {
DOMElement domElement = null;
if (w3cElement instanceof DOMElement) {
domElement = (DOMElement) w3cElement;
} else {
DOMDocument requestDocument = (DOMDocument)
new DOMReader(DOMDocumentFactory.getInstance())
.read(w3cElement.getOwnerDocument());
domElement = (DOMElement)requestDocument.getDocumentElement();
}
return domElement;
}
private boolean validJid(JID jid) {
if (jid.getDomain() == null || jid.getDomain().isEmpty()) {
return false;
}
return true;
}
protected String getBareJID(String address) {
address = address.replaceAll("sip:", "");
int colon = address.indexOf(":");
if (colon != -1) {
address = address.substring(0, colon);
}
return address;
}
protected void sendIqError(IQRequest request, Exception e) {
try {
ErrorMapping error = exceptionMapper.toXmppError(e);
sendIqError(request, error.getType(), error.getCondition(), error.getText());
}
catch (Exception e1) {
throw new IllegalStateException("Cannot dispatch result", e);
}
}
protected static Loggerf getWireLogger() {
return WIRE;
}
protected XmppFactory getXmppFactory() {
return xmppFactory;
}
protected abstract Loggerf getLog();
public AdminService getAdminService() {
return adminService;
}
public void setAdminService(AdminService adminService) {
this.adminService = adminService;
}
public String getLocalDomain() {
return localDomain;
}
public void setExceptionMapper(ExceptionMapper exceptionMapper) {
this.exceptionMapper = exceptionMapper;
}
public ExceptionMapper getExceptionMapper() {
return exceptionMapper;
}
}