/** * 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.openejb.server.discovery; import org.apache.openejb.loader.Options; import org.apache.openejb.server.DiscoveryAgent; import org.apache.openejb.server.DiscoveryListener; import org.apache.openejb.server.SelfManaging; import org.apache.openejb.server.ServerService; import org.apache.openejb.server.ServiceException; import org.apache.openejb.util.LogCategory; import org.apache.openejb.util.Logger; import org.apache.openejb.util.OptionsLog; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.DatagramPacket; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.MulticastSocket; import java.net.Socket; import java.net.SocketTimeoutException; import java.net.URI; import java.util.Properties; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicBoolean; /** * @version $Rev$ $Date$ */ public class MulticastDiscoveryAgent implements DiscoveryAgent, ServerService, SelfManaging { private static final Logger log = Logger.getInstance(LogCategory.OPENEJB_SERVER.createChild("discovery").createChild("multicast"), MulticastDiscoveryAgent.class); private final AtomicBoolean running = new AtomicBoolean(false); private String host = "239.255.3.2"; private int port = 6142; private int timeToLive = 1; private boolean loopbackMode = false; private InetSocketAddress address; private long heartRate = 500; private Tracker tracker; private Multicast multicast; @Override public void init(final Properties props) { final Options options = new Options(props); options.setLogger(new OptionsLog(log)); host = props.getProperty("bind", host); loopbackMode = options.get("loopback_mode", loopbackMode); port = options.get("port", port); heartRate = options.get("heart_rate", heartRate); final Tracker.Builder builder = new Tracker.Builder(); builder.setGroup(props.getProperty("group", builder.getGroup())); builder.setHeartRate(heartRate); builder.setMaxMissedHeartbeats(options.get("max_missed_heartbeats", builder.getMaxMissedHeartbeats())); builder.setMaxReconnectDelay(options.get("max_reconnect_delay", builder.getMaxReconnectDelay())); builder.setReconnectDelay(options.get("reconnect_delay", builder.getReconnectDelay())); builder.setExponentialBackoff(options.get("exponential_backoff", builder.getExponentialBackoff())); builder.setMaxReconnectAttempts(options.get("max_reconnect_attempts", builder.getMaxReconnectAttempts())); tracker = builder.build(); } @Override public String getIP() { return host; } @Override public String getName() { return "multicast"; } @Override public int getPort() { return port; } @Override public void setDiscoveryListener(final DiscoveryListener listener) { this.tracker.setDiscoveryListener(listener); } @Override public void registerService(final URI serviceUri) throws IOException { tracker.registerService(serviceUri); } @Override public void unregisterService(final URI serviceUri) throws IOException { tracker.unregisterService(serviceUri); } @Override public void reportFailed(final URI serviceUri) { tracker.reportFailed(serviceUri); } public static void main(final String[] args) throws Exception { } /** * start the discovery agent * * @throws ServiceException */ @Override public void start() throws ServiceException { try { if (running.compareAndSet(false, true)) { final InetAddress inetAddress = InetAddress.getByName(host); this.address = new InetSocketAddress(inetAddress, port); multicast = new Multicast(tracker); } } catch (Exception e) { throw new ServiceException(e); } } /** * stop the channel * * @throws ServiceException */ @Override public void stop() throws ServiceException { if (running.compareAndSet(true, false)) { multicast.close(); } } @Override public void service(final InputStream in, final OutputStream out) throws ServiceException, IOException { } @Override public void service(final Socket socket) throws ServiceException, IOException { } class Multicast { private static final int BUFF_SIZE = 8192; private final Tracker tracker; private final MulticastSocket multicast; private final Timer timer; private final Thread listenerThread; Multicast(final Tracker tracker) throws IOException { this.tracker = tracker; multicast = new MulticastSocket(port); multicast.setLoopbackMode(loopbackMode); multicast.setTimeToLive(timeToLive); multicast.joinGroup(address.getAddress()); multicast.setSoTimeout((int) heartRate); listenerThread = new Thread(new Listener()); listenerThread.setName("MulticastDiscovery: Listener"); listenerThread.setDaemon(true); listenerThread.start(); final Broadcaster broadcaster = new Broadcaster(); timer = new Timer("MulticastDiscovery: Broadcaster", true); timer.scheduleAtFixedRate(broadcaster, 0, heartRate); } public void close() { timer.cancel(); } class Listener implements Runnable { @Override public void run() { final byte[] buf = new byte[BUFF_SIZE]; final DatagramPacket packet = new DatagramPacket(buf, 0, buf.length); while (running.get()) { tracker.checkServices(); try { multicast.receive(packet); if (packet.getLength() > 0) { final String str = new String(packet.getData(), packet.getOffset(), packet.getLength()); // System.out.println("read = " + str); tracker.processData(str); } } catch (SocketTimeoutException se) { // ignore } catch (IOException e) { if (running.get()) { log.error("failed to process packet: " + e); } } } } } class Broadcaster extends TimerTask { private IOException failed; @Override public void run() { if (running.get()) { heartbeat(); } } private void heartbeat() { for (final String uri : tracker.getRegisteredServices()) { try { final byte[] data = uri.getBytes(); final DatagramPacket packet = new DatagramPacket(data, 0, data.length, address); // System.out.println("ann = " + uri); multicast.send(packet); } catch (IOException e) { // If a send fails, chances are all subsequent sends will fail // too.. No need to keep reporting the // same error over and over. if (failed == null) { failed = e; log.error("Failed to advertise our service: " + uri, e); final String message = e.getMessage(); if (null != message && message.toLowerCase().contains("operation not permitted")) { log.error("The 'Operation not permitted' error has been know to be caused by improper firewall/network setup. " + "Please make sure that the OS is properly configured to allow multicast traffic over: " + multicast.getLocalAddress()); } } } } } } } public String getHost() { return host; } public void setHost(final String host) { this.host = host; } public boolean isLoopbackMode() { return loopbackMode; } public void setLoopbackMode(final boolean loopbackMode) { this.loopbackMode = loopbackMode; } public int getTimeToLive() { return timeToLive; } public void setTimeToLive(final int timeToLive) { this.timeToLive = timeToLive; } }