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.rendezvous;
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 RendezvousDiscoveryAgent implements DiscoveryAgent, ServiceListener {
047    private static final Logger LOG = LoggerFactory.getLogger(RendezvousDiscoveryAgent.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}