001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq.transport.discovery.zeroconf; 018 019import java.io.IOException; 020import java.net.InetAddress; 021import java.net.UnknownHostException; 022import java.util.HashMap; 023import java.util.Iterator; 024import java.util.Map; 025import java.util.concurrent.CopyOnWriteArrayList; 026 027import org.apache.activemq.jmdns.JmDNS; 028import org.apache.activemq.jmdns.ServiceEvent; 029import org.apache.activemq.jmdns.ServiceInfo; 030import org.apache.activemq.jmdns.ServiceListener; 031 032import org.apache.activemq.command.DiscoveryEvent; 033import org.apache.activemq.transport.discovery.DiscoveryAgent; 034import org.apache.activemq.transport.discovery.DiscoveryListener; 035import org.apache.activemq.util.JMSExceptionSupport; 036import org.apache.activemq.util.MapHelper; 037import org.slf4j.Logger; 038import org.slf4j.LoggerFactory; 039 040/** 041 * A {@link DiscoveryAgent} using <a href="http://www.zeroconf.org/">Zeroconf</a> 042 * via the <a href="http://jmdns.sf.net/">jmDNS</a> library 043 * 044 * 045 */ 046public class ZeroconfDiscoveryAgent implements DiscoveryAgent, ServiceListener { 047 private static final Logger LOG = LoggerFactory.getLogger(ZeroconfDiscoveryAgent.class); 048 049 private static final String TYPE_SUFFIX = "ActiveMQ-4."; 050 051 private JmDNS jmdns; 052 private InetAddress localAddress; 053 private String localhost; 054 private int weight; 055 private int priority; 056 057 private DiscoveryListener listener; 058 private String group = "default"; 059 private final CopyOnWriteArrayList<ServiceInfo> serviceInfos = new CopyOnWriteArrayList<ServiceInfo>(); 060 061 // DiscoveryAgent interface 062 // ------------------------------------------------------------------------- 063 public void start() throws Exception { 064 if (group == null) { 065 throw new IOException("You must specify a group to discover"); 066 } 067 String type = getType(); 068 if (!type.endsWith(".")) { 069 LOG.warn("The type '" + type + "' should end with '.' to be a valid Rendezvous type"); 070 type += "."; 071 } 072 try { 073 // force lazy construction 074 getJmdns(); 075 if (listener != null) { 076 LOG.info("Discovering service of type: " + type); 077 jmdns.addServiceListener(type, this); 078 } 079 } catch (IOException e) { 080 JMSExceptionSupport.create("Failed to start JmDNS service: " + e, e); 081 } 082 } 083 084 public void stop() { 085 if (jmdns != null) { 086 for (Iterator<ServiceInfo> iter = serviceInfos.iterator(); iter.hasNext();) { 087 ServiceInfo si = iter.next(); 088 jmdns.unregisterService(si); 089 } 090 091 // Close it down async since this could block for a while. 092 final JmDNS closeTarget = jmdns; 093 Thread thread = new Thread() { 094 public void run() { 095 closeTarget.close(); 096 } 097 }; 098 099 thread.setDaemon(true); 100 thread.start(); 101 102 jmdns = null; 103 } 104 } 105 106 public void registerService(String name) throws IOException { 107 ServiceInfo si = createServiceInfo(name, new HashMap()); 108 serviceInfos.add(si); 109 getJmdns().registerService(si); 110 } 111 112 // ServiceListener interface 113 // ------------------------------------------------------------------------- 114 public void addService(JmDNS jmDNS, String type, String name) { 115 if (LOG.isDebugEnabled()) { 116 LOG.debug("addService with type: " + type + " name: " + name); 117 } 118 if (listener != null) { 119 listener.onServiceAdd(new DiscoveryEvent(name)); 120 } 121 jmDNS.requestServiceInfo(type, name); 122 } 123 124 public void removeService(JmDNS jmDNS, String type, String name) { 125 if (LOG.isDebugEnabled()) { 126 LOG.debug("removeService with type: " + type + " name: " + name); 127 } 128 if (listener != null) { 129 listener.onServiceRemove(new DiscoveryEvent(name)); 130 } 131 } 132 133 public void serviceAdded(ServiceEvent event) { 134 addService(event.getDNS(), event.getType(), event.getName()); 135 } 136 137 public void serviceRemoved(ServiceEvent event) { 138 removeService(event.getDNS(), event.getType(), event.getName()); 139 } 140 141 public void serviceResolved(ServiceEvent event) { 142 } 143 144 public void resolveService(JmDNS jmDNS, String type, String name, ServiceInfo serviceInfo) { 145 } 146 147 public int getPriority() { 148 return priority; 149 } 150 151 public void setPriority(int priority) { 152 this.priority = priority; 153 } 154 155 public int getWeight() { 156 return weight; 157 } 158 159 public void setWeight(int weight) { 160 this.weight = weight; 161 } 162 163 public JmDNS getJmdns() throws IOException { 164 if (jmdns == null) { 165 jmdns = createJmDNS(); 166 } 167 return jmdns; 168 } 169 170 public void setJmdns(JmDNS jmdns) { 171 this.jmdns = jmdns; 172 } 173 174 public InetAddress getLocalAddress() throws UnknownHostException { 175 if (localAddress == null) { 176 localAddress = createLocalAddress(); 177 } 178 return localAddress; 179 } 180 181 public void setLocalAddress(InetAddress localAddress) { 182 this.localAddress = localAddress; 183 } 184 185 public String getLocalhost() { 186 return localhost; 187 } 188 189 public void setLocalhost(String localhost) { 190 this.localhost = localhost; 191 } 192 193 // Implementation methods 194 // ------------------------------------------------------------------------- 195 protected ServiceInfo createServiceInfo(String name, Map map) { 196 int port = MapHelper.getInt(map, "port", 0); 197 198 String type = getType(); 199 200 if (LOG.isDebugEnabled()) { 201 LOG.debug("Registering service type: " + type + " name: " + name + " details: " + map); 202 } 203 return new ServiceInfo(type, name + "." + type, port, weight, priority, ""); 204 } 205 206 protected JmDNS createJmDNS() throws IOException { 207 return JmDNSFactory.create(getLocalAddress()); 208 } 209 210 protected InetAddress createLocalAddress() throws UnknownHostException { 211 if (localhost != null) { 212 return InetAddress.getByName(localhost); 213 } 214 return InetAddress.getLocalHost(); 215 } 216 217 public void setDiscoveryListener(DiscoveryListener listener) { 218 this.listener = listener; 219 } 220 221 public String getGroup() { 222 return group; 223 } 224 225 public void setGroup(String group) { 226 this.group = group; 227 } 228 229 public String getType() { 230 return "_" + group + "." + TYPE_SUFFIX; 231 } 232 233 public void serviceFailed(DiscoveryEvent event) throws IOException { 234 // TODO: is there a way to notify the JmDNS that the service failed? 235 } 236 237}