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 */
017 package org.apache.activemq.transport.discovery.zeroconf;
018
019 import java.io.IOException;
020 import java.net.InetAddress;
021 import java.net.UnknownHostException;
022 import java.util.HashMap;
023 import java.util.Iterator;
024 import java.util.Map;
025 import java.util.concurrent.CopyOnWriteArrayList;
026
027 import org.apache.activemq.jmdns.JmDNS;
028 import org.apache.activemq.jmdns.ServiceEvent;
029 import org.apache.activemq.jmdns.ServiceInfo;
030 import org.apache.activemq.jmdns.ServiceListener;
031
032 import org.apache.activemq.command.DiscoveryEvent;
033 import org.apache.activemq.transport.discovery.DiscoveryAgent;
034 import org.apache.activemq.transport.discovery.DiscoveryListener;
035 import org.apache.activemq.util.JMSExceptionSupport;
036 import org.apache.activemq.util.MapHelper;
037 import org.slf4j.Logger;
038 import 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 */
046 public 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 }