001    /**
002     * 
003     * Copyright 2004 Protique Ltd
004     * 
005     * Licensed under the Apache License, Version 2.0 (the "License"); 
006     * you may not use this file except in compliance with the License. 
007     * 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     **/
018    package org.activemq.transport.zeroconf;
019    
020    import org.apache.commons.logging.Log;
021    import org.apache.commons.logging.LogFactory;
022    import org.activemq.ConfigurationException;
023    import org.activemq.NotStartedException;
024    import org.activemq.transport.DiscoveryAgent;
025    import org.activemq.transport.DiscoveryAgentSupport;
026    import org.activemq.transport.DiscoveryEvent;
027    import org.activemq.util.JMSExceptionHelper;
028    import org.activemq.util.MapHelper;
029    
030    import javax.jmdns.JmDNS;
031    import javax.jmdns.ServiceEvent;
032    import javax.jmdns.ServiceInfo;
033    import javax.jmdns.ServiceListener;
034    import javax.jms.JMSException;
035    import java.io.IOException;
036    import java.net.InetAddress;
037    import java.net.URI;
038    import java.net.UnknownHostException;
039    import java.util.Enumeration;
040    import java.util.HashMap;
041    import java.util.Hashtable;
042    import java.util.Map;
043    
044    /**
045     * A {@link DiscoveryAgent} using <a href="http://www.zeroconf.org/">Zeroconf</a>
046     * via the <a href="http://jmdns.sf.net/">jmDNS</a> library
047     *
048     * @version $Revision$
049     */
050    public class ZeroconfDiscoveryAgent extends DiscoveryAgentSupport implements ServiceListener {
051        private static final Log log = LogFactory.getLog(ZeroconfDiscoveryAgent.class);
052    
053        private JmDNS jmdns;
054        private InetAddress localAddress;
055        private String localhost;
056        private String type;
057        private int weight = 0;
058        private int priority = 0;
059    
060        // DiscoveryAgent interface
061        //-------------------------------------------------------------------------
062        public void start() throws JMSException {
063            if (type == null) {
064                throw new ConfigurationException("You must specify a type of service to discover");
065            }
066            if (!type.endsWith(".")) {
067                log.warn("The type '" + type + "' should end with '.' to be a valid Zeroconf type");
068                type += ".";
069            }
070            try {
071                if (jmdns == null) {
072                    jmdns = createJmDNS();
073                }
074                if (!listeners.isEmpty()) {
075                    log.info("Discovering service of type: " + type);
076                    jmdns.addServiceListener(type, this);
077                }
078            }
079            catch (IOException e) { 
080                JMSExceptionHelper.newJMSException("Failed to start JmDNS service: " + e, e);
081            }
082        }
083    
084        public void stop() throws JMSException {
085            jmdns.unregisterAllServices();
086            jmdns.close();
087        }
088    
089        public void registerService(String name, Map details) throws JMSException {
090            if (jmdns == null) {
091                throw new NotStartedException();
092            }
093            try {
094                jmdns.registerService(createServiceInfo(name, details));
095            }
096            catch (IOException e) {
097                JMSExceptionHelper.newJMSException("Could not register service: " + name + ". Reason: " + e, e);
098            }
099        }
100    
101    
102        // ServiceListener interface
103        //-------------------------------------------------------------------------
104        public void addService(JmDNS jmDNS, String type, String name) {
105            if (log.isDebugEnabled()) {
106                log.debug("addService with type: " + type + " name: " + name);
107            }
108            jmDNS.requestServiceInfo(type, name);
109        }
110    
111        public void removeService(JmDNS jmDNS, String type, String name) {
112            if (log.isDebugEnabled()) {
113                log.debug("removeService with type: " + type + " name: " + name);
114            }
115            DiscoveryEvent event = new DiscoveryEvent(this, name);
116            fireRemoveService(event);
117        }
118    
119        public void resolveService(JmDNS jmDNS, String type, String name, ServiceInfo serviceInfo) {
120            if (log.isDebugEnabled()) {
121                log.debug("removeService with type: " + type + " name: " + name + " info: " + serviceInfo);
122            }
123    
124            Map map = new HashMap();
125            if (serviceInfo != null) {
126                Enumeration iter = serviceInfo.getPropertyNames();
127                while (iter.hasMoreElements()) {
128                    String key = (String) iter.nextElement();
129                    String value = serviceInfo.getPropertyString(key);
130                    map.put(key, value);
131                }
132            }
133            String urlStr = (String)map.get("connectURL");
134            if (urlStr != null) { 
135                try { 
136                    boolean reliable = false;
137                    if (urlStr.startsWith("reliable:")){ 
138                       reliable = true;
139                       urlStr = urlStr.substring("reliable:".length());
140                    }
141                    URI temp = new URI(urlStr);
142            
143                    temp = new URI(temp.getScheme(), temp.getUserInfo(), serviceInfo.getHostAddress(), temp.getPort(),
144                        temp.getPath(), temp.getQuery(), temp.getFragment());
145                    String newUrl = temp.toString();
146                    if (reliable) { 
147                        newUrl = "reliable:"+newUrl;
148                    }
149                    map.put("connectURL", newUrl);
150                 }
151                 catch (Exception e) { 
152                     log.error("Failed to resolve URL " + urlStr + " to an IP address: ", e);
153                 }
154            }
155            DiscoveryEvent event = new DiscoveryEvent(this, name, map);
156            fireAddService(event);
157        }
158    
159        public String getType() {
160            return type;
161        }
162    
163        public void setType(String type) {
164            this.type = type;
165        }
166    
167        public int getPriority() {
168            return priority;
169        }
170    
171        public void setPriority(int priority) {
172            this.priority = priority;
173        }
174    
175        public int getWeight() {
176            return weight;
177        }
178    
179        public void setWeight(int weight) {
180            this.weight = weight;
181        }
182    
183        public JmDNS getJmdns() {
184            return jmdns;
185        }
186    
187        public void setJmdns(JmDNS jmdns) {
188            this.jmdns = jmdns;
189        }
190    
191    
192        public InetAddress getLocalAddress() throws UnknownHostException {
193            if (localAddress == null) {
194                localAddress = createLocalAddress();
195            }
196            return localAddress;
197        }
198    
199        public void setLocalAddress(InetAddress localAddress) {
200            this.localAddress = localAddress;
201        }
202    
203        public String getLocalhost() {
204            return localhost;
205        }
206    
207        public void setLocalhost(String localhost) {
208            this.localhost = localhost;
209        }
210    
211        // Implementation methods
212        //-------------------------------------------------------------------------
213        protected ServiceInfo createServiceInfo(String name, Map map) {
214            name += "." + type;
215            int port = MapHelper.getInt(map, "port", 0);
216    
217            if (log.isDebugEnabled()) {
218                log.debug("Registering service type: " + type + " name: " + name + " details: " + map);
219            }
220            return new ServiceInfo(type, name, port, weight, priority, new Hashtable(map));
221        }
222    
223        protected JmDNS createJmDNS() throws IOException {
224            return new JmDNS(getLocalAddress());
225        }
226    
227        protected InetAddress createLocalAddress() throws UnknownHostException {
228            if (localhost != null) {
229                return InetAddress.getByName(localhost);
230            }
231            return InetAddress.getLocalHost();
232        }
233    
234        public void serviceAdded(ServiceEvent event) {
235        }
236    
237        public void serviceRemoved(ServiceEvent event) {
238        }
239    
240        public void serviceResolved(ServiceEvent event) {
241        }
242    }