/** * Copyright (C) 2010-2017 Structr GmbH * * This file is part of Structr <http://structr.org>. * * Structr is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * Structr is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Structr. If not, see <http://www.gnu.org/licenses/>. */ package org.structr.xmpp; import java.util.HashMap; import java.util.List; import java.util.Map; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Presence.Mode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.structr.common.PropertyView; import org.structr.common.SecurityContext; import org.structr.common.View; import org.structr.common.error.ErrorBuffer; import org.structr.common.error.FrameworkException; import org.structr.core.Export; import org.structr.core.app.App; import org.structr.core.app.StructrApp; import org.structr.core.entity.AbstractNode; import org.structr.core.graph.ModificationQueue; import org.structr.core.graph.NodeAttribute; import org.structr.core.graph.Tx; import org.structr.core.property.BooleanProperty; import org.structr.core.property.EndNodes; import org.structr.core.property.EnumProperty; import org.structr.core.property.FunctionProperty; import org.structr.core.property.IntProperty; import org.structr.core.property.Property; import org.structr.core.property.PropertyMap; import org.structr.core.property.StringProperty; import org.structr.rest.RestMethodResult; import org.structr.schema.SchemaService; /** * * */ public class XMPPClient extends AbstractNode implements XMPPInfo { private static final Logger logger = LoggerFactory.getLogger(XMPPClient.class.getName()); public static final Property<List<XMPPRequest>> pendingRequests = new EndNodes<>("pendingRequests", XMPPClientRequest.class); public static final Property<String> xmppHandle = new FunctionProperty("xmppHandle").format("concat(this.xmppUsername, '@', this.xmppHost)").indexed(); public static final Property<String> xmppUsername = new StringProperty("xmppUsername").indexed(); public static final Property<String> xmppPassword = new StringProperty("xmppPassword"); public static final Property<String> xmppService = new StringProperty("xmppService"); public static final Property<String> xmppHost = new StringProperty("xmppHost"); public static final Property<Integer> xmppPort = new IntProperty("xmppPort"); public static final Property<Mode> presenceMode = new EnumProperty("presenceMode", Mode.class, Mode.available); public static final Property<Boolean> isEnabled = new BooleanProperty("isEnabled"); public static final Property<Boolean> isConnected = new BooleanProperty("isConnected"); static { SchemaService.registerBuiltinTypeOverride("XMPPClient", XMPPClient.class.getName()); } public static final View publicView = new View(XMPPClient.class, PropertyView.Public, xmppHandle, xmppUsername, xmppPassword, xmppService, xmppHost, xmppPort, presenceMode, isEnabled, isConnected, pendingRequests ); public static final View uiView = new View(XMPPClient.class, PropertyView.Ui, xmppHandle, xmppUsername, xmppPassword, xmppService, xmppHost, xmppPort, presenceMode, isEnabled, isConnected, pendingRequests ); @Override public boolean onCreation(final SecurityContext securityContext, final ErrorBuffer errorBuffer) throws FrameworkException { if (getProperty(isEnabled)) { XMPPContext.connect(this); } return super.onCreation(securityContext, errorBuffer); } @Override public boolean onModification(final SecurityContext securityContext, final ErrorBuffer errorBuffer, final ModificationQueue modificationQueue) throws FrameworkException { XMPPClientConnection connection = XMPPContext.getClientForId(getUuid()); boolean enabled = getProperty(isEnabled); if (!enabled) { if (connection != null && connection.isConnected()) { connection.disconnect(); } } else { if (connection == null || !connection.isConnected()) { XMPPContext.connect(this); } connection = XMPPContext.getClientForId(getUuid()); if (connection != null) { if (connection.isConnected()) { setProperty(isConnected, true); connection.setPresence(getProperty(presenceMode)); } else { setProperty(isConnected, false); } } } return super.onModification(securityContext, errorBuffer, modificationQueue); } @Override public boolean onDeletion(final SecurityContext securityContext, final ErrorBuffer errorBuffer, final PropertyMap properties) throws FrameworkException { final String uuid = properties.get(id); if (uuid != null) { final XMPPClientConnection connection = XMPPContext.getClientForId(uuid); if (connection != null) { connection.disconnect(); } } return super.onDeletion(securityContext, errorBuffer, properties); } @Override public String getUsername() { return getProperty(xmppUsername); } @Override public String getPassword() { return getProperty(xmppPassword); } @Override public String getService() { return getProperty(xmppService); } @Override public String getHostName() { return getProperty(xmppHost); } @Override public int getPort() { return getProperty(xmppPort); } @Export public RestMethodResult doSendMessage(final String recipient, final String message) throws FrameworkException { if (getProperty(isEnabled)) { final XMPPClientConnection connection = XMPPContext.getClientForId(getUuid()); if (connection.isConnected()) { connection.sendMessage(recipient, message); } else { throw new FrameworkException(422, "Not connected."); } } return new RestMethodResult(200); } @Export public RestMethodResult doSubscribe(final String recipient) throws FrameworkException { if (getProperty(isEnabled)) { final XMPPClientConnection connection = XMPPContext.getClientForId(getUuid()); if (connection.isConnected()) { connection.subscribe(recipient); } else { throw new FrameworkException(422, "Not connected."); } } return new RestMethodResult(200); } @Export public RestMethodResult doUnsubscribe(final String recipient) throws FrameworkException { if (getProperty(isEnabled)) { final XMPPClientConnection connection = XMPPContext.getClientForId(getUuid()); if (connection.isConnected()) { connection.unsubscribe(recipient); } else { throw new FrameworkException(422, "Not connected."); } } return new RestMethodResult(200); } @Export public RestMethodResult doConfirmSubscription(final String recipient) throws FrameworkException { if (getProperty(isEnabled)) { final XMPPClientConnection connection = XMPPContext.getClientForId(getUuid()); if (connection.isConnected()) { connection.confirmSubscription(recipient); } else { throw new FrameworkException(422, "Not connected."); } } return new RestMethodResult(200); } @Export public RestMethodResult doDenySubscription(final String recipient) throws FrameworkException { if (getProperty(isEnabled)) { final XMPPClientConnection connection = XMPPContext.getClientForId(getUuid()); if (connection.isConnected()) { connection.denySubscription(recipient); } else { throw new FrameworkException(422, "Not connected."); } } return new RestMethodResult(200); } @Export public RestMethodResult doJoinChat(final String chatRoom, final String nickname, final String password) throws FrameworkException { if (getProperty(isEnabled)) { final XMPPClientConnection connection = XMPPContext.getClientForId(getUuid()); if (connection.isConnected()) { connection.joinChat(chatRoom, nickname, password); } else { throw new FrameworkException(422, "Not connected."); } } return new RestMethodResult(200); } @Export public RestMethodResult doSendChatMessage(final String chatRoom, final String message, final String password) throws FrameworkException { if (getProperty(isEnabled)) { final XMPPClientConnection connection = XMPPContext.getClientForId(getUuid()); if (connection.isConnected()) { connection.sendChatMessage(chatRoom, message, password); } else { throw new FrameworkException(422, "Not connected."); } } return new RestMethodResult(200); } // ----- static methods ----- public static void onMessage(final String uuid, final Message message) { final App app = StructrApp.getInstance(); try (final Tx tx = app.tx()) { final XMPPClient client = StructrApp.getInstance().get(XMPPClient.class, uuid); if (client != null) { final String callbackName = "onXMPP" + message.getClass().getSimpleName(); final Map<String, Object> properties = new HashMap<>(); properties.put("sender", message.getFrom()); properties.put("message", message.getBody()); client.invokeMethod(callbackName, properties, false); } tx.success(); } catch (FrameworkException fex) { logger.warn("", fex); } } public static void onRequest(final String uuid, final IQ request) { final App app = StructrApp.getInstance(); try (final Tx tx = app.tx()) { final XMPPClient client = StructrApp.getInstance().get(XMPPClient.class, uuid); if (client != null) { app.create(XMPPRequest.class, new NodeAttribute(XMPPRequest.client, client), new NodeAttribute(XMPPRequest.sender, request.getFrom()), new NodeAttribute(XMPPRequest.owner, client.getProperty(XMPPClient.owner)), new NodeAttribute(XMPPRequest.content, request.toXML().toString()), new NodeAttribute(XMPPRequest.requestType, request.getType()) ); } tx.success(); } catch (FrameworkException fex) { logger.warn("", fex); } } }