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 javax.jmdns.JmDNS; 028import javax.jmdns.ServiceEvent; 029import javax.jmdns.ServiceInfo; 030import javax.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 042 * href="http://www.zeroconf.org/">Zeroconf</a> via the <a 043 * href="http://jmdns.sf.net/">jmDNS</a> library 044 */ 045public class ZeroconfDiscoveryAgent implements DiscoveryAgent, ServiceListener { 046 private static final Logger LOG = LoggerFactory.getLogger(ZeroconfDiscoveryAgent.class); 047 048 private static final String TYPE_SUFFIX = "ActiveMQ-5."; 049 050 private JmDNS jmdns; 051 private InetAddress localAddress; 052 private String localhost; 053 private int weight; 054 private int priority; 055 private String typeSuffix = TYPE_SUFFIX; 056 057 private DiscoveryListener listener; 058 private String group = "default"; 059 private final CopyOnWriteArrayList<ServiceInfo> serviceInfos = 060 new CopyOnWriteArrayList<ServiceInfo>(); 061 062 // DiscoveryAgent interface 063 // ------------------------------------------------------------------------- 064 @Override 065 public void start() throws Exception { 066 if (group == null) { 067 throw new IOException("You must specify a group to discover"); 068 } 069 String type = getType(); 070 if (!type.endsWith(".")) { 071 LOG.warn("The type '{}' should end with '.' to be a valid Rendezvous type", type); 072 type += "."; 073 } 074 try { 075 // force lazy construction 076 getJmdns(); 077 if (listener != null) { 078 LOG.info("Discovering service of type: {}", type); 079 jmdns.addServiceListener(type, this); 080 } 081 } catch (IOException e) { 082 JMSExceptionSupport.create("Failed to start JmDNS service: " + e, e); 083 } 084 } 085 086 @Override 087 public void stop() { 088 if (jmdns != null) { 089 for (Iterator<ServiceInfo> iter = serviceInfos.iterator(); iter.hasNext();) { 090 ServiceInfo si = iter.next(); 091 jmdns.unregisterService(si); 092 } 093 094 // Close it down async since this could block for a while. 095 final JmDNS closeTarget = jmdns; 096 Thread thread = new Thread() { 097 @Override 098 public void run() { 099 try { 100 if (JmDNSFactory.onClose(getLocalAddress())) { 101 closeTarget.close(); 102 }; 103 } catch (IOException e) { 104 LOG.debug("Error closing JmDNS {}. This exception will be ignored.", getLocalhost(), e); 105 } 106 } 107 }; 108 109 thread.setDaemon(true); 110 thread.start(); 111 112 jmdns = null; 113 } 114 } 115 116 @Override 117 public void registerService(String name) throws IOException { 118 ServiceInfo si = createServiceInfo(name, new HashMap<String, Object>()); 119 serviceInfos.add(si); 120 getJmdns().registerService(si); 121 } 122 123 // ServiceListener interface 124 // ------------------------------------------------------------------------- 125 public void addService(JmDNS jmDNS, String type, String name) { 126 LOG.debug("addService with type: {} name: {}", type, name); 127 if (listener != null) { 128 listener.onServiceAdd(new DiscoveryEvent(name)); 129 } 130 jmDNS.requestServiceInfo(type, name); 131 } 132 133 public void removeService(JmDNS jmDNS, String type, String name) { 134 LOG.debug("removeService with type: {} name: {}", type, name); 135 if (listener != null) { 136 listener.onServiceRemove(new DiscoveryEvent(name)); 137 } 138 } 139 140 @Override 141 public void serviceAdded(ServiceEvent event) { 142 addService(event.getDNS(), event.getType(), event.getName()); 143 } 144 145 @Override 146 public void serviceRemoved(ServiceEvent event) { 147 removeService(event.getDNS(), event.getType(), event.getName()); 148 } 149 150 @Override 151 public void serviceResolved(ServiceEvent event) { 152 } 153 154 public void resolveService(JmDNS jmDNS, String type, String name, ServiceInfo serviceInfo) { 155 } 156 157 public int getPriority() { 158 return priority; 159 } 160 161 public void setPriority(int priority) { 162 this.priority = priority; 163 } 164 165 public int getWeight() { 166 return weight; 167 } 168 169 public void setWeight(int weight) { 170 this.weight = weight; 171 } 172 173 public JmDNS getJmdns() throws IOException { 174 if (jmdns == null) { 175 jmdns = createJmDNS(); 176 } 177 return jmdns; 178 } 179 180 public void setJmdns(JmDNS jmdns) { 181 this.jmdns = jmdns; 182 } 183 184 public InetAddress getLocalAddress() throws UnknownHostException { 185 if (localAddress == null) { 186 localAddress = createLocalAddress(); 187 } 188 return localAddress; 189 } 190 191 public void setLocalAddress(InetAddress localAddress) { 192 this.localAddress = localAddress; 193 } 194 195 public String getLocalhost() { 196 return localhost; 197 } 198 199 public void setLocalhost(String localhost) { 200 this.localhost = localhost; 201 } 202 203 // Implementation methods 204 // ------------------------------------------------------------------------- 205 protected ServiceInfo createServiceInfo(String name, Map map) { 206 int port = MapHelper.getInt(map, "port", 0); 207 String type = getType(); 208 LOG.debug("Registering service type: {} name: {} details: {}", new Object[]{type, name, map}); 209 return ServiceInfo.create(type, name + "." + type, port, weight, priority, ""); 210 } 211 212 protected JmDNS createJmDNS() throws IOException { 213 return JmDNSFactory.create(getLocalAddress()); 214 } 215 216 protected InetAddress createLocalAddress() throws UnknownHostException { 217 if (localhost != null) { 218 return InetAddress.getByName(localhost); 219 } 220 return InetAddress.getLocalHost(); 221 } 222 223 @Override 224 public void setDiscoveryListener(DiscoveryListener listener) { 225 this.listener = listener; 226 } 227 228 public String getGroup() { 229 return group; 230 } 231 232 public void setGroup(String group) { 233 this.group = group; 234 } 235 236 public void setType(String typeSuffix) { 237 this.typeSuffix = typeSuffix; 238 } 239 240 public String getType() { 241 if (typeSuffix == null || typeSuffix.isEmpty()) { 242 typeSuffix = TYPE_SUFFIX; 243 } 244 245 return "_" + group + "." + typeSuffix; 246 } 247 248 @Override 249 public void serviceFailed(DiscoveryEvent event) throws IOException { 250 // TODO: is there a way to notify the JmDNS that the service failed? 251 } 252}