content: /**
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package org.apache.activemq.transport.discovery.zeroconf;
  
  import java.io.IOException;
  import java.net.InetAddress;
  import java.net.UnknownHostException;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.Map;
  import java.util.concurrent.CopyOnWriteArrayList;
  
  import javax.jmdns.JmDNS;
  import javax.jmdns.ServiceEvent;
  import javax.jmdns.ServiceInfo;
  import javax.jmdns.ServiceListener;
  
  import org.apache.activemq.command.DiscoveryEvent;
  import org.apache.activemq.transport.discovery.DiscoveryAgent;
  import org.apache.activemq.transport.discovery.DiscoveryListener;
  import org.apache.activemq.util.JMSExceptionSupport;
  import org.apache.activemq.util.MapHelper;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  /**
   * A {@link DiscoveryAgent} using <a
   * href="http://www.zeroconf.org/">Zeroconf</a> via the <a
   * href="http://jmdns.sf.net/">jmDNS</a> library
   */
  public class ZeroconfDiscoveryAgent implements DiscoveryAgent, ServiceListener {
      private static final Logger LOG = LoggerFactory.getLogger(ZeroconfDiscoveryAgent.class);
  
      private static final String TYPE_SUFFIX = "ActiveMQ-5.";
  
      private JmDNS jmdns;
      private InetAddress localAddress;
      private String localhost;
      private int weight;
      private int priority;
      private String typeSuffix = TYPE_SUFFIX;
  
      private DiscoveryListener listener;
      private String group = "default";
      private final CopyOnWriteArrayList<ServiceInfo> serviceInfos =
          new CopyOnWriteArrayList<ServiceInfo>();
  
      // DiscoveryAgent interface
      // -------------------------------------------------------------------------
      @Override
      public void start() throws Exception {
          if (group == null) {
              throw new IOException("You must specify a group to discover");
          }
          String type = getType();
          if (!type.endsWith(".")) {
              LOG.warn("The type '{}' should end with '.' to be a valid Rendezvous type", type);
              type += ".";
          }
          try {
              // force lazy construction
              getJmdns();
              if (listener != null) {
                  LOG.info("Discovering service of type: {}", type);
                  jmdns.addServiceListener(type, this);
              }
          } catch (IOException e) {
              throw JMSExceptionSupport.create("Failed to start JmDNS service: " + e, e);
          }
      }
  
      @Override
      public void stop() {
          if (jmdns != null) {
              for (Iterator<ServiceInfo> iter = serviceInfos.iterator(); iter.hasNext();) {
                  ServiceInfo si = iter.next();
                  jmdns.unregisterService(si);
              }
  
              // Close it down async since this could block for a while.
              final JmDNS closeTarget = jmdns;
              Thread thread = new Thread() {
                  @Override
                  public void run() {
                      try {
                          if (JmDNSFactory.onClose(getLocalAddress())) {
                              closeTarget.close();
                          };
                      } catch (IOException e) {
                          LOG.debug("Error closing JmDNS {}. This exception will be ignored.", getLocalhost(), e);
                      }
                  }
              };
  
              thread.setDaemon(true);
              thread.start();
  
              jmdns = null;
          }
      }
  
      @Override
      public void registerService(String name) throws IOException {
          ServiceInfo si = createServiceInfo(name, new HashMap<String, Object>());
          serviceInfos.add(si);
          getJmdns().registerService(si);
      }
  
      // ServiceListener interface
      // -------------------------------------------------------------------------
      public void addService(JmDNS jmDNS, String type, String name) {
          LOG.debug("addService with type: {} name: {}", type, name);
          if (listener != null) {
              listener.onServiceAdd(new DiscoveryEvent(name));
          }
          jmDNS.requestServiceInfo(type, name);
      }
  
      public void removeService(JmDNS jmDNS, String type, String name) {
          LOG.debug("removeService with type: {} name: {}", type, name);
          if (listener != null) {
              listener.onServiceRemove(new DiscoveryEvent(name));
          }
      }
  
      @Override
      public void serviceAdded(ServiceEvent event) {
          addService(event.getDNS(), event.getType(), event.getName());
      }
  
      @Override
      public void serviceRemoved(ServiceEvent event) {
          removeService(event.getDNS(), event.getType(), event.getName());
      }
  
      @Override
      public void serviceResolved(ServiceEvent event) {
      }
  
      public void resolveService(JmDNS jmDNS, String type, String name, ServiceInfo serviceInfo) {
      }
  
      public int getPriority() {
          return priority;
      }
  
      public void setPriority(int priority) {
          this.priority = priority;
      }
  
      public int getWeight() {
          return weight;
      }
  
      public void setWeight(int weight) {
          this.weight = weight;
      }
  
      public JmDNS getJmdns() throws IOException {
          if (jmdns == null) {
              jmdns = createJmDNS();
          }
          return jmdns;
      }
  
      public void setJmdns(JmDNS jmdns) {
          this.jmdns = jmdns;
      }
  
      public InetAddress getLocalAddress() throws UnknownHostException {
          if (localAddress == null) {
              localAddress = createLocalAddress();
          }
          return localAddress;
      }
  
      public void setLocalAddress(InetAddress localAddress) {
          this.localAddress = localAddress;
      }
  
      public String getLocalhost() {
          return localhost;
      }
  
      public void setLocalhost(String localhost) {
          this.localhost = localhost;
      }
  
      // Implementation methods
      // -------------------------------------------------------------------------
      protected ServiceInfo createServiceInfo(String name, Map map) {
          int port = MapHelper.getInt(map, "port", 0);
          String type = getType();
          LOG.debug("Registering service type: {} name: {} details: {}", new Object[]{type, name, map});
          return ServiceInfo.create(type, name + "." + type, port, weight, priority, "");
      }
  
      protected JmDNS createJmDNS() throws IOException {
          return JmDNSFactory.create(getLocalAddress());
      }
  
      protected InetAddress createLocalAddress() throws UnknownHostException {
          if (localhost != null) {
              return InetAddress.getByName(localhost);
          }
          return InetAddress.getLocalHost();
      }
  
      @Override
      public void setDiscoveryListener(DiscoveryListener listener) {
          this.listener = listener;
      }
  
      public String getGroup() {
          return group;
      }
  
      public void setGroup(String group) {
          this.group = group;
      }
  
      public void setType(String typeSuffix) {
          this.typeSuffix = typeSuffix;
      }
  
      public String getType() {
          if (typeSuffix == null || typeSuffix.isEmpty()) {
              typeSuffix = TYPE_SUFFIX;
          }
  
          return "_" + group + "." + typeSuffix;
      }
  
      @Override
      public void serviceFailed(DiscoveryEvent event) throws IOException {
          // TODO: is there a way to notify the JmDNS that the service failed?
      }
  }
  