Package org.eclipse.ecf.provider.jmdns.container

Source Code of org.eclipse.ecf.provider.jmdns.container.JMDNSDiscoveryContainer

/****************************************************************************
* Copyright (c) 2007 Composent, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*    Composent, Inc. - initial API and implementation
*****************************************************************************/
package org.eclipse.ecf.provider.jmdns.container;

import java.io.*;
import java.net.URI;
import java.util.*;
import javax.jmdns.*;
import javax.jmdns.ServiceInfo;
import org.eclipse.core.runtime.Assert;
import org.eclipse.ecf.core.ContainerConnectException;
import org.eclipse.ecf.core.events.*;
import org.eclipse.ecf.core.identity.ID;
import org.eclipse.ecf.core.identity.IDFactory;
import org.eclipse.ecf.core.security.IConnectContext;
import org.eclipse.ecf.core.util.*;
import org.eclipse.ecf.discovery.*;
import org.eclipse.ecf.discovery.identity.*;
import org.eclipse.ecf.discovery.service.IDiscoveryService;
import org.eclipse.ecf.internal.provider.jmdns.*;
import org.eclipse.ecf.provider.jmdns.identity.JMDNSNamespace;

public class JMDNSDiscoveryContainer extends AbstractDiscoveryContainerAdapter implements IDiscoveryService, ServiceListener, ServiceTypeListener {

  private static final String SCHEME_PROPERTY = "jmdns.ptcl"; //$NON-NLS-1$
  private static final String URI_PATH_PROPERTY = "path"; //$NON-NLS-1$
  private static final String NAMING_AUTHORITY_PROPERTY = "jmdns.namingauthority"; //$NON-NLS-1$

  public static final int DEFAULT_REQUEST_TIMEOUT = 3000;

  private static int instanceCount = 0;

  JmDNS jmdns = null;
  private ID targetID = null;

  List serviceTypes = null;

  /**
   * Map of IServiceInfos (maps to SRV type records) discovered by mDNS.
   * mDNS defines a two stage process where a client first discovers the PTR (announced by the advertiser) and then upon
   * request from the application layers above resolves the PTR into a SRV.
   * Deregistration is only send for PTR records, not for SRV records (SRV records have a very low TTL anyway).
   */
  final Map services = Collections.synchronizedMap(new HashMap());

  boolean disposed = false;
  final Object lock = new Object();

  SimpleFIFOQueue queue = null;
  Thread notificationThread = null;

  /**
   * @since 4.0
   */
  public JMDNSDiscoveryContainer() {
    super(JMDNSNamespace.NAME, new DiscoveryContainerConfig(IDFactory.getDefault().createStringID(JMDNSDiscoveryContainer.class.getName() + ";" + instanceCount++))); //$NON-NLS-1$  //$NON-NLS-2$
    serviceTypes = new ArrayList();
  }

  /****************** IContainer methods **************************/

  /* (non-Javadoc)
   * @see org.eclipse.ecf.core.IContainer#getConnectedID()
   */
  public ID getConnectedID() {
    return this.targetID;
  }

  /* (non-Javadoc)
   * @see org.eclipse.ecf.discovery.AbstractDiscoveryContainerAdapter#dispose()
   */
  public void dispose() {
    synchronized (lock) {
      super.dispose();
      disposed = true;
    }
  }

  /* (non-Javadoc)
   * @see org.eclipse.ecf.core.IContainer#connect(org.eclipse.ecf.core.identity.ID, org.eclipse.ecf.core.security.IConnectContext)
   */
  public void connect(final ID targetID1, final IConnectContext joinContext) throws ContainerConnectException {
    synchronized (lock) {
      if (disposed)
        throw new ContainerConnectException("Container has been disposed"); //$NON-NLS-1$
      if (this.targetID != null)
        throw new ContainerConnectException("Already connected"); //$NON-NLS-1$
      this.targetID = (targetID1 == null) ? getConfig().getID() : targetID1;
      fireContainerEvent(new ContainerConnectingEvent(this.getID(), this.targetID, joinContext));
      initializeQueue();
      try {
        this.jmdns = JmDNS.create();
        jmdns.addServiceTypeListener(this);
      } catch (final IOException e) {
        Trace.catching(JMDNSPlugin.PLUGIN_ID, JMDNSDebugOptions.EXCEPTIONS_CATCHING, this.getClass(), "connect", e); //$NON-NLS-1$
        if (this.jmdns != null) {
          jmdns.close();
          jmdns = null;
        }
        throw new ContainerConnectException("Cannot create JmDNS instance", e); //$NON-NLS-1$
      }
      fireContainerEvent(new ContainerConnectedEvent(this.getID(), this.targetID));
    }
  }

  private void initializeQueue() {
    queue = new SimpleFIFOQueue();
    notificationThread = new Thread(new Runnable() {
      public void run() {
        while (!disposed || queue.isStopped()) {
          if (Thread.currentThread().isInterrupted())
            break;
          final Runnable runnable = (Runnable) queue.dequeue();
          if (Thread.currentThread().isInterrupted() || runnable == null)
            break;
          try {
            runnable.run();
          } catch (final Throwable t) {
            JMDNSPlugin plugin = JMDNSPlugin.getDefault();
            if (plugin != null) {
              plugin.logException("handleRuntimeException", t); //$NON-NLS-1$
            }
          }
        }
      }
    }, "JMDNS Discovery Thread"); //$NON-NLS-1$
    notificationThread.start();
  }

  /* (non-Javadoc)
   * @see org.eclipse.ecf.core.IContainer#disconnect()
   */
  public void disconnect() {
    synchronized (lock) {
      if (getConnectedID() == null || disposed) {
        return;
      }
      final ID connectedID = getConnectedID();
      fireContainerEvent(new ContainerDisconnectingEvent(this.getID(), connectedID));
      queue.close();
      notificationThread.interrupt();
      notificationThread = null;
      this.targetID = null;
      serviceTypes.clear();
      // @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=385395
      if (jmdns != null) {
        jmdns.close();
        jmdns = null;
      }
      fireContainerEvent(new ContainerDisconnectedEvent(this.getID(), connectedID));
    }
  }

  /************************* IDiscoveryContainerAdapter methods *********************/

  /* (non-Javadoc)
   * @see org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#getServiceInfo(org.eclipse.ecf.discovery.identity.IServiceID)
   */
  public IServiceInfo getServiceInfo(final IServiceID service) {
    Assert.isNotNull(service);
    synchronized (lock) {
      try {
        // ECF discovery API defines identity to be the service type and the URI (location)
        // see https://bugs.eclipse.org/266723
        final ServiceInfo[] serviceInfos = jmdns.list(service.getServiceTypeID().getInternal());
        for (int i = 0; i < serviceInfos.length; i++) {
          ServiceInfo serviceInfo = serviceInfos[i];
          IServiceInfo iServiceInfo = createIServiceInfoFromServiceInfo(serviceInfo);
          Assert.isNotNull(iServiceInfo);
          Assert.isNotNull(iServiceInfo.getServiceID());
          if (iServiceInfo.getServiceID().equals(service)) {
            return iServiceInfo;
          }
        }
        return null;
      } catch (final Exception e) {
        Trace.catching(JMDNSPlugin.PLUGIN_ID, JMDNSDebugOptions.EXCEPTIONS_CATCHING, this.getClass(), "getServiceInfo", e); //$NON-NLS-1$
        return null;
      }
    }
  }

  /* (non-Javadoc)
   * @see org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#getServices()
   */
  public IServiceInfo[] getServices() {
    synchronized (lock) {
      final IServiceTypeID[] serviceTypeArray = getServiceTypes();
      final List results = new ArrayList();
      for (int i = 0; i < serviceTypeArray.length; i++) {
        final IServiceTypeID stid = serviceTypeArray[i];
        if (stid != null)
          results.addAll(Arrays.asList(getServices(stid)));
      }
      return (IServiceInfo[]) results.toArray(new IServiceInfo[] {});
    }
  }

  /* (non-Javadoc)
   * @see org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#getServices(org.eclipse.ecf.discovery.identity.IServiceTypeID)
   */
  public IServiceInfo[] getServices(final IServiceTypeID type) {
    Assert.isNotNull(type);
    final List serviceInfos = new ArrayList();
    synchronized (lock) {
      // We don't know the naming authority yet (it's part of the service properties)
      for (final Iterator itr = serviceTypes.iterator(); itr.hasNext();) {
        final IServiceTypeID serviceType = (IServiceTypeID) itr.next();
        if (Arrays.equals(serviceType.getServices(), type.getServices()) && Arrays.equals(serviceType.getProtocols(), type.getProtocols()) && Arrays.equals(serviceType.getScopes(), type.getScopes())) {
          final ServiceInfo[] infos = jmdns.list(type.getInternal());
          for (int i = 0; i < infos.length; i++) {
            try {
              if (infos[i] != null) {
                final IServiceInfo si = createIServiceInfoFromServiceInfo(infos[i]);
                if (si != null)
                  serviceInfos.add(si);
              }
            } catch (final Exception e) {
              Trace.catching(JMDNSPlugin.PLUGIN_ID, JMDNSDebugOptions.EXCEPTIONS_CATCHING, this.getClass(), "getServices", e); //$NON-NLS-1$
            }
          }
        }
      }
      return (IServiceInfo[]) serviceInfos.toArray(new IServiceInfo[] {});
    }
  }

  /* (non-Javadoc)
   * @see org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#getServiceTypes()
   */
  public IServiceTypeID[] getServiceTypes() {
    synchronized (lock) {
      return (IServiceTypeID[]) serviceTypes.toArray(new IServiceTypeID[] {});
    }
  }

  /* (non-Javadoc)
   * @see org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#registerService(org.eclipse.ecf.discovery.IServiceInfo)
   */
  public void registerService(final IServiceInfo serviceInfo) {
    Assert.isNotNull(serviceInfo);
    final ServiceInfo svcInfo = createServiceInfoFromIServiceInfo(serviceInfo);
    checkServiceInfo(svcInfo);
    try {
      jmdns.registerService(svcInfo);
    } catch (final IOException e) {
      throw new ECFRuntimeException("Exception registering service", e); //$NON-NLS-1$
    }
  }

  /* (non-Javadoc)
   * @see org.eclipse.ecf.discovery.IDiscoveryContainerAdapter#unregisterService(org.eclipse.ecf.discovery.IServiceInfo)
   */
  public void unregisterService(final IServiceInfo serviceInfo) {
    Assert.isNotNull(serviceInfo);
    final ServiceInfo si = createServiceInfoFromIServiceInfo(serviceInfo);
    jmdns.unregisterService(si);
  }

  /* (non-Javadoc)
   * @see org.eclipse.ecf.discovery.AbstractDiscoveryContainerAdapter#unregisterAllServices()
   */
  public void unregisterAllServices() {
    jmdns.unregisterAllServices();
  }

  /* (non-Javadoc)
   * @see org.eclipse.ecf.discovery.AbstractDiscoveryContainerAdapter#purgeCache()
   */
  public IServiceInfo[] purgeCache() {
    synchronized (lock) {
      serviceTypes.clear();
    }
    return super.purgeCache();
  }

  /**************************** JMDNS listeners ***********************************/

  private void runInThread(final Runnable runnable) {
    queue.enqueue(runnable);
  }

  /* (non-Javadoc)
   * @see javax.jmdns.ServiceTypeListener#serviceTypeAdded(javax.jmdns.ServiceEvent)
   */
  public void serviceTypeAdded(final ServiceEvent arg0) {
    Trace.trace(JMDNSPlugin.PLUGIN_ID, "serviceTypeAdded(" + arg0.getType() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
    arg0.getDNS().addServiceListener(arg0.getType(), JMDNSDiscoveryContainer.this);
  }

  void fireTypeDiscovered(final IServiceTypeID serviceType) {
    fireServiceTypeDiscovered(new ServiceTypeContainerEvent(serviceType, getID()));
  }

  /* (non-Javadoc)
   * @see javax.jmdns.ServiceListener#serviceAdded(javax.jmdns.ServiceEvent)
   */
  public void serviceAdded(final ServiceEvent arg0) {
    Trace.trace(JMDNSPlugin.PLUGIN_ID, "serviceAdded(" + arg0.getName() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
    runInThread(new Runnable() {
      public void run() {
        final String serviceType = arg0.getType();
        final String serviceName = arg0.getName();
        IServiceInfo aServiceInfo = null;
        synchronized (lock) {
          if (getConnectedID() == null || disposed) {
            return;
          }

          // explicitly get the service to determine the naming authority (part of the service properties)
          try {
            final ServiceInfo info = arg0.getDNS().getServiceInfo(serviceType, serviceName);
            aServiceInfo = createIServiceInfoFromServiceInfo(info);
            services.put(serviceType + serviceName, aServiceInfo);
            serviceTypes.add(aServiceInfo.getServiceID().getServiceTypeID());
          } catch (final Exception e) {
            Trace.trace(JMDNSPlugin.PLUGIN_ID, "Failed to resolve in serviceAdded(" + arg0.getName() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
            return;
          }
        }
        fireTypeDiscovered(aServiceInfo.getServiceID().getServiceTypeID());
        fireDiscovered(aServiceInfo);
      }
    });
  }

  /* (non-Javadoc)
   * @see javax.jmdns.ServiceListener#serviceRemoved(javax.jmdns.ServiceEvent)
   */
  public void serviceRemoved(final ServiceEvent arg0) {
    Trace.trace(JMDNSPlugin.PLUGIN_ID, "serviceRemoved(" + arg0.getName() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
    runInThread(new Runnable() {
      public void run() {
        if (getConnectedID() == null || disposed) {
          return;
        }
        final String serviceType = arg0.getType();
        final String serviceName = arg0.getName();
        IServiceInfo aServiceInfo = (IServiceInfo) services.remove(serviceType + serviceName);
        if (aServiceInfo == null) {
          Trace.trace(JMDNSPlugin.PLUGIN_ID, "Failed to resolve in serviceRemoved(" + arg0.getName() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
          return;
        }
        fireUndiscovered(aServiceInfo);
      }
    });
  }

  void fireUndiscovered(final IServiceInfo serviceInfo) {
    fireServiceUndiscovered(new ServiceContainerEvent(serviceInfo, getID()));
  }

  /* (non-Javadoc)
   * @see javax.jmdns.ServiceListener#serviceResolved(javax.jmdns.ServiceEvent)
   */
  public void serviceResolved(final ServiceEvent arg0) {
    Trace.trace(JMDNSPlugin.PLUGIN_ID, "serviceResolved(" + arg0.getName() + ")"); //$NON-NLS-1$ //$NON-NLS-2$
  }

  void fireDiscovered(final IServiceInfo serviceInfo) {
    fireServiceDiscovered(new ServiceContainerEvent(serviceInfo, getID()));
  }

  /*******************************************/
  private void checkServiceInfo(final ServiceInfo serviceInfo) {
    final String serviceName = serviceInfo.getName();
    if (serviceName == null)
      throw new ECFRuntimeException("Service name cannot be null"); //$NON-NLS-1$
  }

  IServiceInfo createIServiceInfoFromServiceInfo(final ServiceInfo serviceInfo) throws Exception {
    Assert.isNotNull(serviceInfo);
    final int priority = serviceInfo.getPriority();
    final int weight = serviceInfo.getWeight();
    final Properties props = new Properties();
    String uriProtocol = null;
    String uriPath = null;
    String namingAuthority = IServiceTypeID.DEFAULT_NA;
    for (final Enumeration e = serviceInfo.getPropertyNames(); e.hasMoreElements();) {
      final String key = (String) e.nextElement();
      if (SCHEME_PROPERTY.equals(key)) {
        uriProtocol = serviceInfo.getPropertyString(key);
      } else if (NAMING_AUTHORITY_PROPERTY.equals(key)) {
        namingAuthority = serviceInfo.getPropertyString(key);
      } else if (URI_PATH_PROPERTY.equals(key)) {
        uriPath = serviceInfo.getPropertyString(key);
      } else {
        final byte[] bytes = serviceInfo.getPropertyBytes(key);
        try {
          final ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bytes));
          final Object object = in.readObject();
          in.close();
          props.put(key, object);
        } catch (final StreamCorruptedException ioe) {
          props.put(key, serviceInfo.getPropertyString(key));
        } catch (final EOFException eofe) { // not all byte[] are serialized objs (e.g. a native service)
          props.put(key, serviceInfo.getPropertyString(key));
        }
      }
    }

    // proto
    final String proto = serviceInfo.getProtocol();
    // scopes
    final String domain = serviceInfo.getDomain();
    final String[] scopes = new String[] {domain};

    // uri
    String authority = serviceInfo.getHostAddress() + ":" + serviceInfo.getPort();
    final URI uri = new URI(uriProtocol == null ? proto : uriProtocol, authority, uriPath, null, null);
    // service type
    String st = serviceInfo.getType();
    final int end = st.indexOf(proto);
    String[] types = StringUtils.split(st.substring(1, end), "._");
    final IServiceTypeID sID = ServiceIDFactory.getDefault().createServiceTypeID(getServicesNamespace(), types, scopes, new String[] {proto}, namingAuthority);

    // service name
    final String name = serviceInfo.getName();

    return new org.eclipse.ecf.discovery.ServiceInfo(uri, name, sID, priority, weight, new ServiceProperties(props));
  }

  private ServiceInfo createServiceInfoFromIServiceInfo(final IServiceInfo serviceInfo) {
    if (serviceInfo == null)
      return null;
    final IServiceID sID = serviceInfo.getServiceID();
    final Hashtable props = new Hashtable();
    final IServiceProperties svcProps = serviceInfo.getServiceProperties();
    if (svcProps != null) {
      for (final Enumeration e = svcProps.getPropertyNames(); e.hasMoreElements();) {
        final String key = (String) e.nextElement();
        final Object val = svcProps.getProperty(key);
        if (val instanceof String) {
          props.put(key, val);
        } else if (val instanceof Serializable) {
          final ByteArrayOutputStream bos = new ByteArrayOutputStream();
          try {
            final ObjectOutputStream out = new ObjectOutputStream(bos);
            out.writeObject(val);
            out.close();
          } catch (final IOException e1) {
            e1.printStackTrace();
          }
          final byte[] buf = bos.toByteArray();
          props.put(key, buf);
          //        } else if (svcProps.getPropertyBytes(key) != null) {
          //          byte[] bytes = svcProps.getPropertyBytes(key);
          //          props.put(key, bytes);
        } else if (val != null) {
          props.put(key, val.toString());
        }
      }
    }
    // Add URI scheme to props
    final URI location = serviceInfo.getServiceID().getLocation();
    if (location != null) {
      props.put(SCHEME_PROPERTY, location.getScheme());
      props.put(URI_PATH_PROPERTY, location.getPath());
    }
    props.put(NAMING_AUTHORITY_PROPERTY, serviceInfo.getServiceID().getServiceTypeID().getNamingAuthority());
    final ServiceInfo si = ServiceInfo.create(sID.getServiceTypeID().getInternal(), serviceInfo.getServiceName(), location.getPort(), serviceInfo.getWeight(), serviceInfo.getPriority(), props);
    return si;
  }

  /* (non-Javadoc)
   * @see org.eclipse.ecf.discovery.AbstractDiscoveryContainerAdapter#getContainerName()
   */
  public String getContainerName() {
    return JMDNSPlugin.NAME;
  }
}
TOP

Related Classes of org.eclipse.ecf.provider.jmdns.container.JMDNSDiscoveryContainer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.