Java tutorial
package de.raion.xmppbot; /* * #%L * XmppBot Core * %% * Copyright (C) 2012 Bernd Kiefer * %% * Licensed 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. * #L% */ import java.io.File; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import net.dharwin.common.tools.cli.api.CLIContext; import net.dharwin.common.tools.cli.api.CommandLineApplication; import net.dharwin.common.tools.cli.api.annotations.CLICommand; import net.dharwin.common.tools.cli.api.annotations.CLIEntry; import net.dharwin.common.tools.cli.api.exceptions.CLIInitException; import net.dharwin.common.tools.cli.api.utils.CLIAnnotationDiscovereryListener; import org.jivesoftware.smack.Chat; import org.jivesoftware.smack.ChatManagerListener; import org.jivesoftware.smack.Connection; import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.SmackConfiguration; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.filter.AndFilter; import org.jivesoftware.smack.filter.FromContainsFilter; import org.jivesoftware.smack.filter.NotFilter; import org.jivesoftware.smackx.muc.DiscussionHistory; import org.jivesoftware.smackx.muc.MultiUserChat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.ObjectMapper; import com.impetus.annovention.ClasspathDiscoverer; import com.impetus.annovention.Discoverer; import de.raion.xmppbot.annotation.MultiUserChatListener; import de.raion.xmppbot.annotation.PacketInterceptor; import de.raion.xmppbot.command.core.AbstractXmppCommand; import de.raion.xmppbot.config.BotConfiguration; import de.raion.xmppbot.config.XmppConfiguration; import de.raion.xmppbot.plugin.AbstractMessageListenerPlugin; import de.raion.xmppbot.plugin.MessageListenerPlugin; import de.raion.xmppbot.plugin.PluginStatusListener; /** *<h2>Enbot Botson</h2> * *a simple xmppbot providing a framework for commands and plugins.<br> * *@see AbstractXmppCommand for commands *@see CLICommand marker annotation for commands *@see AbstractMessageListenerPlugin for plugins *@see MessageListenerPlugin marker annotation for plugins */ @SuppressWarnings("rawtypes") @CLIEntry public class XmppBot extends CommandLineApplication implements ChatManagerListener, PluginStatusListener { private static Logger log = LoggerFactory.getLogger(XmppBot.class); private Map<String, XMPPConnection> connectionMap; private Map<String, MultiUserChat> multiUserChatMap; private Map<String, Chat> chatMap; private HashMap<MultiUserChat, Set<String>> multiUserChatPresenceMap; private ChatMessageListener messageHandler; private Map<String, Class<PacketInterceptor>> packetInterceptorMap; private Map<String, Class<MultiUserChatListener>> multiUserChatListenerMap; private BotConfiguration configuration; /** * constructor * @throws CLIInitException if the initilization of the CommandLineInterface fails */ public XmppBot() throws CLIInitException { connectionMap = new HashMap<String, XMPPConnection>(); multiUserChatMap = new HashMap<String, MultiUserChat>(); chatMap = new HashMap<String, Chat>(); multiUserChatPresenceMap = new HashMap<MultiUserChat, Set<String>>(); messageHandler = new ChatMessageListener(this); packetInterceptorMap = loadPacketInterceptors(); multiUserChatListenerMap = loadMultiUserChatListener(); } /** * initializes the bot with the given configuration * @param aConfig the configuration to use */ @SuppressWarnings("unchecked") public void init(BotConfiguration aConfig) { try { configuration = aConfig; super._commands = loadCommands(); Map<String, ConnectionConfiguration> conConfigMap = prepareConnectionConfiguration( aConfig.getConfigurations()); connectionMap = initConnections(aConfig, conConfigMap); this.registerChatListener(this, connectionMap); getContext().init(); Collection<XMPPConnection> connections = connectionMap.values(); for (XMPPConnection connection : connections) { addPlugins(connection); } } catch (Exception e) { log.error("init(BotConfiguration) - ", e); } } /** * @return context with thread-specific settings */ public XmppContext getContext() { return (XmppContext) _appContext; } /** implementation of the ChatManagerListener interface.<br> * method is called when a new chat request is incoming * @param chat the incoming chat * @param createdLocally true if local created, otherwise false * @see org.jivesoftware.smack.ChatManagerListener#chatCreated(org.jivesoftware.smack.Chat, boolean) */ public void chatCreated(Chat chat, boolean createdLocally) { if (!createdLocally) { chat.addMessageListener(messageHandler); log.info("incoming chat from {} with threadId {}", chat.getParticipant(), chat.getThreadID()); chatMap.put(chat.getParticipant().trim(), chat); } } /** * processes a incoming command * @param cmdString the command as string */ public void processCommand(String cmdString) { log.debug("Thread = " + Thread.currentThread().getName()); super.processInputLine(cmdString); } /** * get multiuserchat by name * @param mucName name of the multiuserchat * @return multiuserchat or null if not available */ public MultiUserChat getMultiUserChat(String mucName) { return this.multiUserChatMap.get(mucName); } /** * marks aUser for the MultiUserChat muc as available * @param muc MultiUserChat * @param aUser the user */ public void userAvailable(MultiUserChat muc, String aUser) { if (this.multiUserChatPresenceMap.containsKey(muc)) { this.multiUserChatPresenceMap.get(muc).add(aUser); } else { HashSet<String> userSet = new HashSet<String>(); userSet.add(aUser); this.multiUserChatPresenceMap.put(muc, userSet); } } /** * removes aUser from the {@link #multiUserChatPresenceMap} mapped by muc * @param muc MultiUserChat as key * @param aUser the user to mark as unavailable */ public void userUnavailabe(MultiUserChat muc, String aUser) { if (this.multiUserChatPresenceMap.containsKey(muc)) { this.multiUserChatPresenceMap.get(muc).remove(aUser); } } /** * the available user for a certain multiuserchat * @param muc multiuserchat * @return available user names */ public Set<String> getAvailableUser(MultiUserChat muc) { return multiUserChatPresenceMap.get(muc); } /** * chat by name * @param participant user name * @return the chat or null if not available */ public Chat getChat(String participant) { return chatMap.get(participant); } /** * Load the necessary commands for this application. * * @return The map of commands. * @throws CLIInitException when the initialization of the CLIContext failed */ private Map<String, Class<AbstractXmppCommand>> loadCommands() throws CLIInitException { Discoverer discoverer = new ClasspathDiscoverer(); CLIAnnotationDiscovereryListener discoveryListener = new CLIAnnotationDiscovereryListener( new String[] { CLICommand.class.getName() }); discoverer.addAnnotationListener(discoveryListener); discoverer.discover(); return loadCommands(discoveryListener.getDiscoveredClasses()); } private Map<String, Class<AbstractXmppCommand>> loadCommands(List<String> commandClasses) throws CLIInitException { Map<String, Class<AbstractXmppCommand>> commandMap = new HashMap<String, Class<AbstractXmppCommand>>(); for (String commandClassName : commandClasses) { try { @SuppressWarnings("unchecked") Class<AbstractXmppCommand> commandClass = (Class<AbstractXmppCommand>) Class .forName(commandClassName); if (AbstractXmppCommand.class.isAssignableFrom(commandClass)) { CLICommand annotation = commandClass.getAnnotation(CLICommand.class); commandMap.put(annotation.name().toLowerCase(), commandClass); log.debug("Loaded command [" + annotation.name() + "]."); } } catch (ClassNotFoundException e) { throw new CLIInitException("Unable to find command class [" + commandClassName + "]."); } catch (Exception e) { throw new CLIInitException( "Unable to load command class [" + commandClassName + "]: " + e.getMessage()); } } return commandMap; } /** * maps the configuration information for Xmpp connections into smackx ConnectionConfiguration * @param configMap the configuration info to use * @return smackx ConnectionConfiguration mapped by the configured name */ private Map<String, ConnectionConfiguration> prepareConnectionConfiguration( Map<String, XmppConfiguration> configMap) { Map<String, ConnectionConfiguration> connections = new HashMap<String, ConnectionConfiguration>(); Set<String> keySet = configMap.keySet(); for (String key : keySet) { XmppConfiguration xmppConfig = configMap.get(key); String host = xmppConfig.getHost(); int port = xmppConfig.getPort(); String serviceName = xmppConfig.getServiceName(); if (serviceName == null || serviceName.equals("")) { serviceName = host; } connections.put(key.toLowerCase(), new ConnectionConfiguration(host, port, serviceName)); } return connections; } /** * establishes the XMPPConnections with the given BotConfiguration and * the XMPP ConnectionConfiguration and joins the configured MultiUserChats and Chats * @param aConfig the BotConfiguration to use * @param connectionConfigurationMap the ConnectionConfigurations to use for establishing * XMPPConnections mapped by configured name * @return established XMPPConnections mapped by the configured name from the BotConfiguration */ private Map<String, XMPPConnection> initConnections(BotConfiguration aConfig, Map<String, ConnectionConfiguration> connectionConfigurationMap) { Map<String, XMPPConnection> aConnectionMap = new HashMap<String, XMPPConnection>(); Map<String, XmppConfiguration> xmppConfigMap = aConfig.getConfigurations(); Connection.DEBUG_ENABLED = aConfig.isXmppConnectionDebuggingEnabled(); Set<String> keySet = connectionConfigurationMap.keySet(); for (String key : keySet) { ConnectionConfiguration cc = connectionConfigurationMap.get(key); XmppConfiguration xmppConfig = xmppConfigMap.get(key); try { XMPPConnection connection = new XMPPConnection(cc); connection.connect(); log.info("connection established to server '{}'", xmppConfig.getHost()); String jabberId = xmppConfig.getJabberId() + "/bot"; String pwd = xmppConfig.getPassword(); connection.login(jabberId, pwd); log.info("logged in with name '{}'", jabberId); joinMultiUserChats(xmppConfig, connection); joinChats(xmppConfig, connection); aConnectionMap.put(key, connection); } catch (XMPPException e) { log.error("login failed to server {} with nickname {}", xmppConfig.getHost(), xmppConfig.getNickName()); } } return aConnectionMap; } private XMPPConnection addPlugins(XMPPConnection connection) { Collection<AbstractMessageListenerPlugin> plugins = getContext().getPluginManager().getEnabledPlugins() .values(); // excluding messages from enbot himself :) List<String> nickNameList = getOwnNickNames(); List<NotFilter> notFromFilterList = new ArrayList<NotFilter>(); for (String nickName : nickNameList) { FromContainsFilter fromFilter = new FromContainsFilter(nickName); notFromFilterList.add(new NotFilter(fromFilter)); } for (AbstractMessageListenerPlugin plugin : plugins) { NotFilter[] notFilter = new NotFilter[notFromFilterList.size()]; AndFilter andFilter = new AndFilter(notFromFilterList.toArray(notFilter)); andFilter.addFilter(plugin.getAcceptFilter()); connection.addPacketListener(plugin, andFilter); } return connection; } private List<String> getOwnNickNames() { List<String> list = new ArrayList<String>(); Collection<XmppConfiguration> c = configuration.getConfigurations().values(); for (XmppConfiguration xmppConfiguration : c) { list.add(xmppConfiguration.getNickName()); } return list; } private void joinMultiUserChats(XmppConfiguration xmppConfig, XMPPConnection connection) { Collection<String> mucNameCollection = xmppConfig.getMultiUserChats().values(); for (String mucName : mucNameCollection) { try { if (packetInterceptorMap.containsKey(xmppConfig.getServiceType())) { AbstractPacketInterceptor interceptor = (AbstractPacketInterceptor) packetInterceptorMap .get(xmppConfig.getServiceType()).newInstance(); interceptor.setContext(getContext()); connection.addPacketInterceptor(interceptor, interceptor.getPacketFilter()); } // start TODO remove workareound handly of muclistener DiscussionHistory history = new DiscussionHistory(); history.setMaxStanzas(0); MultiUserChat muc = new MultiUserChat(connection, mucName); if (multiUserChatListenerMap.containsKey(xmppConfig.getServiceType())) { Class<MultiUserChatListener> mucListenerClass = multiUserChatListenerMap .get(xmppConfig.getServiceType()); Constructor<MultiUserChatListener> constructor = mucListenerClass.getConstructor(XmppBot.class); AbstractMultiUserChatListener mucListener = (AbstractMultiUserChatListener) constructor .newInstance(this); muc.addMessageListener(mucListener); } muc.join(xmppConfig.getNickName(), xmppConfig.getPassword(), history, SmackConfiguration.getPacketReplyTimeout()); log.info("joined multiuserchat '{}' with address {}", mucName, muc.getRoom()); this.multiUserChatMap.put(mucName, muc); // TODO maybe removing Iterator<String> it = muc.getOccupants(); while (it.hasNext()) { userAvailable(muc, it.next()); } } catch (Exception e) { log.error("Exception caught in joinChannels: " + e.getMessage(), e); } } } private Map<String, Class<PacketInterceptor>> loadPacketInterceptors() { Discoverer discoverer = new ClasspathDiscoverer(); CLIAnnotationDiscovereryListener discoveryListener = new CLIAnnotationDiscovereryListener( new String[] { PacketInterceptor.class.getName() }); discoverer.addAnnotationListener(discoveryListener); discoverer.discover(); List<String> list = discoveryListener.getDiscoveredClasses(); HashMap<String, Class<PacketInterceptor>> map = new HashMap<String, Class<PacketInterceptor>>(); for (String className : list) { try { @SuppressWarnings("unchecked") Class<PacketInterceptor> clazz = (Class<PacketInterceptor>) Class.forName(className); PacketInterceptor annotation = clazz.getAnnotation(PacketInterceptor.class); map.put(annotation.service().toLowerCase(), clazz); } catch (ClassNotFoundException e) { log.error("loadPacketInterceptors()", e); } } return map; } // TODO reduce redundance of duplicated code use template instead private Map<String, Class<MultiUserChatListener>> loadMultiUserChatListener() { Discoverer discoverer = new ClasspathDiscoverer(); CLIAnnotationDiscovereryListener discoveryListener = new CLIAnnotationDiscovereryListener( new String[] { MultiUserChatListener.class.getName() }); discoverer.addAnnotationListener(discoveryListener); discoverer.discover(); List<String> list = discoveryListener.getDiscoveredClasses(); HashMap<String, Class<MultiUserChatListener>> map = new HashMap<String, Class<MultiUserChatListener>>(); for (String className : list) { try { @SuppressWarnings("unchecked") Class<MultiUserChatListener> clazz = (Class<MultiUserChatListener>) Class.forName(className); MultiUserChatListener annotation = clazz.getAnnotation(MultiUserChatListener.class); map.put(annotation.service().toLowerCase(), clazz); } catch (ClassNotFoundException e) { log.error("loadMultiUserChatListener()", e); } } return map; } // TODO implement! private void joinChats(XmppConfiguration xmppConfig, XMPPConnection connection) { Set<String> keySet = xmppConfig.getChats().keySet(); } private void registerChatListener(ChatManagerListener chatListener, Map<String, XMPPConnection> conMap) { Set<String> keySet = conMap.keySet(); for (String key : keySet) { XMPPConnection connection = conMap.get(key); connection.getChatManager().addChatListener(chatListener); } } /** * <b>does nothing! disables shutdown</b> * @see net.dharwin.common.tools.cli.api.CommandLineApplication#shutdown() */ @Override protected void shutdown() { /* do nothing here */} @Override protected CLIContext createContext() { return new XmppContext(this); } /** * @return configuration object of enbot */ public BotConfiguration getConfiguration() { return configuration; } /** * checks if Command command is available * @param command cmd * @return true if available otherwise false */ public boolean hasCommand(String command) { return getCommandNames().contains(command); } public <T> void pluginDisabled(String pluginName, AbstractMessageListenerPlugin<T> plugin) { // TODO Auto-generated method stub } public <T> void pluginEnabled(String pluginName, AbstractMessageListenerPlugin<T> plugin) { // TODO Auto-generated method stub } /** * starting the xmppbot * @param args arguments, arg[0] should link to the named configfile, otherwise * Enbot will lookup for <code>xmppbot.json</code> in the workingdirectory * @throws Exception if an not expected Exception occure */ public static void main(String[] args) throws Exception { XmppBot bot = new XmppBot(); File configFile = null; if (args.length == 0) { String fileName = bot.getContext().getString("xmppbot.configuration.filename", "xmppbot.json"); configFile = new File(fileName); } else { configFile = new File(args[0]); } log.info(configFile.getAbsolutePath()); ObjectMapper mapper = new ObjectMapper(); BotConfiguration config = mapper.readValue(configFile, BotConfiguration.class); log.debug(config.toString()); bot.init(config); TimeUnit.HOURS.sleep(1); } }