/** * 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.flume.sink.irc; import java.io.IOException; import org.apache.flume.Channel; import org.apache.flume.ChannelException; import org.apache.flume.Context; import org.apache.flume.CounterGroup; import org.apache.flume.Event; import org.apache.flume.EventDeliveryException; import org.apache.flume.Transaction; import org.apache.flume.conf.Configurable; import org.apache.flume.sink.AbstractSink; import org.schwering.irc.lib.IRCConnection; import org.schwering.irc.lib.IRCEventListener; import org.schwering.irc.lib.IRCModeParser; import org.schwering.irc.lib.IRCUser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Preconditions; public class IRCSink extends AbstractSink implements Configurable { private static final Logger logger = LoggerFactory.getLogger(IRCSink.class); private static final int DEFAULT_PORT = 6667; private static final String DEFAULT_SPLIT_CHARS = "\n"; private static final String IRC_CHANNEL_PREFIX = "#"; private IRCConnection connection = null; private String hostname; private Integer port; private String nick; private String password; private String user; private String name; private String chan; private Boolean splitLines; private String splitChars; private CounterGroup counterGroup; static public class IRCConnectionListener implements IRCEventListener { public void onRegistered() { } public void onDisconnected() { logger.error("IRC sink disconnected"); } public void onError(String msg) { logger.error("IRC sink error: {}", msg); } public void onError(int num, String msg) { logger.error("IRC sink error: {} - {}", num, msg); } public void onInvite(String chan, IRCUser u, String nickPass) { } public void onJoin(String chan, IRCUser u) { } public void onKick(String chan, IRCUser u, String nickPass, String msg) { } public void onMode(IRCUser u, String nickPass, String mode) { } public void onMode(String chan, IRCUser u, IRCModeParser mp) { } public void onNick(IRCUser u, String nickNew) { } public void onNotice(String target, IRCUser u, String msg) { } public void onPart(String chan, IRCUser u, String msg) { } public void onPrivmsg(String chan, IRCUser u, String msg) { } public void onQuit(IRCUser u, String msg) { } public void onReply(int num, String value, String msg) { } public void onTopic(String chan, IRCUser u, String topic) { } public void onPing(String p) { } public void unknown(String a, String b, String c, String d) { } } public IRCSink() { counterGroup = new CounterGroup(); } public void configure(Context context) { hostname = context.getString("hostname"); String portStr = context.getString("port"); nick = context.getString("nick"); password = context.getString("password"); user = context.getString("user"); name = context.getString("name"); chan = context.getString("chan"); splitLines = context.getBoolean("splitlines"); splitChars = context.getString("splitchars"); if (portStr != null) { port = Integer.parseInt(portStr); } else { port = DEFAULT_PORT; } if (splitChars == null) { splitChars = DEFAULT_SPLIT_CHARS; } Preconditions.checkState(hostname != null, "No hostname specified"); Preconditions.checkState(nick != null, "No nick specified"); Preconditions.checkState(chan != null, "No chan specified"); } private void createConnection() throws IOException { if (connection == null) { logger.debug( "Creating new connection to hostname:{} port:{}", hostname, port); connection = new IRCConnection(hostname, new int[] { port }, password, nick, user, name); connection.addIRCEventListener(new IRCConnectionListener()); connection.setEncoding("UTF-8"); connection.setPong(true); connection.setDaemon(false); connection.setColors(false); connection.connect(); connection.send("join " + IRC_CHANNEL_PREFIX + chan); } } private void destroyConnection() { if (connection != null) { logger.debug("Destroying connection to: {}:{}", hostname, port); connection.close(); } connection = null; } @Override public void start() { logger.info("IRC sink starting"); try { createConnection(); } catch (Exception e) { logger.error("Unable to create irc client using hostname:" + hostname + " port:" + port + ". Exception follows.", e); /* Try to prevent leaking resources. */ destroyConnection(); /* FIXME: Mark ourselves as failed. */ return; } super.start(); logger.debug("IRC sink {} started", this.getName()); } @Override public void stop() { logger.info("IRC sink {} stopping", this.getName()); destroyConnection(); super.stop(); logger.debug("IRC sink {} stopped. Metrics:{}", this.getName(), counterGroup); } private void sendLine(Event event) { String body = new String(event.getBody()); if (splitLines) { String[] lines = body.split(splitChars); for(String line: lines) { connection.doPrivmsg(IRC_CHANNEL_PREFIX + this.chan, line); } } else { connection.doPrivmsg(IRC_CHANNEL_PREFIX + this.chan, body); } } @Override public Status process() throws EventDeliveryException { Status status = Status.READY; Channel channel = getChannel(); Transaction transaction = channel.getTransaction(); try { transaction.begin(); createConnection(); Event event = channel.take(); if (event == null) { counterGroup.incrementAndGet("event.empty"); status = Status.BACKOFF; } else { sendLine(event); counterGroup.incrementAndGet("event.irc"); } transaction.commit(); } catch (ChannelException e) { transaction.rollback(); logger.error( "Unable to get event from channel. Exception follows.", e); status = Status.BACKOFF; } catch (Exception e) { transaction.rollback(); logger.error( "Unable to communicate with IRC server. Exception follows.", e); status = Status.BACKOFF; destroyConnection(); } finally { transaction.close(); } return status; } }