/**
*
*/
package javax.jmdns.impl;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.jmdns.JmDNS;
import javax.jmdns.JmmDNS;
import javax.jmdns.NetworkTopologyDiscovery;
import javax.jmdns.NetworkTopologyEvent;
import javax.jmdns.NetworkTopologyListener;
import javax.jmdns.ServiceInfo;
import javax.jmdns.ServiceListener;
import javax.jmdns.ServiceTypeListener;
import javax.jmdns.impl.constants.DNSConstants;
import javax.jmdns.impl.util.NamedThreadFactory;
/**
* This class enable multihoming mDNS. It will open a mDNS per IP address of the
* machine.
*
* @author Cédrik Lime, Pierre Frisch
*/
public class JmmDNSImpl
implements
JmmDNS,
NetworkTopologyListener,
ServiceInfoImpl.Delegate
{
private static Logger logger = Logger.getLogger(JmmDNSImpl.class.getName());
private final Set<NetworkTopologyListener> _networkListeners;
/**
* Every JmDNS created.
*/
private final ConcurrentMap<InetAddress, JmDNS> _knownMDNS;
/**
* This enable the service info text update.
*/
private final ConcurrentMap<String, ServiceInfo> _services;
/**
* List of registered services
*/
private final Set<String> _serviceTypes;
/**
* Holds instances of ServiceListener's. Keys are Strings holding a fully
* qualified service type. Values are LinkedList's of ServiceListener's.
*/
private final ConcurrentMap<String, List<ServiceListener>> _serviceListeners;
/**
* Holds instances of ServiceTypeListener's.
*/
private final Set<ServiceTypeListener> _typeListeners;
private final ExecutorService _listenerExecutor;
private final ExecutorService _jmDNSExecutor;
private final Timer _timer;
private final AtomicBoolean _isClosing;
private final AtomicBoolean _closed;
/**
*
*/
public JmmDNSImpl()
{
super();
_networkListeners = Collections
.synchronizedSet(new HashSet<NetworkTopologyListener>());
_knownMDNS = new ConcurrentHashMap<InetAddress, JmDNS>();
_services = new ConcurrentHashMap<String, ServiceInfo>(20);
_listenerExecutor = Executors.newSingleThreadExecutor(new NamedThreadFactory(
"JmmDNS Listeners"));
_jmDNSExecutor = Executors.newCachedThreadPool(new NamedThreadFactory("JmmDNS"));
_timer = new Timer("Multihomed mDNS.Timer", true);
_serviceListeners = new ConcurrentHashMap<String, List<ServiceListener>>();
_typeListeners = Collections.synchronizedSet(new HashSet<ServiceTypeListener>());
_serviceTypes = Collections.synchronizedSet(new HashSet<String>());
(new NetworkChecker(this, NetworkTopologyDiscovery.Factory.getInstance()))
.start(_timer);
_isClosing = new AtomicBoolean(false);
_closed = new AtomicBoolean(false);
}
/*
* (non-Javadoc)
*
* @see java.io.Closeable#close()
*/
@Override
public void close() throws IOException
{
if (_isClosing.compareAndSet(false, true))
{
if (logger.isLoggable(Level.FINER))
{
logger.finer("Cancelling JmmDNS: " + this);
}
_timer.cancel();
_listenerExecutor.shutdown();
_jmDNSExecutor.shutdown();
// We need to cancel all the DNS
ExecutorService executor = Executors
.newCachedThreadPool(new NamedThreadFactory("JmmDNS.close"));
try
{
for (final JmDNS mDNS : this.getDNS())
{
executor.submit(new Runnable()
{
/**
* {@inheritDoc}
*/
@Override
public void run()
{
try
{
mDNS.close();
}
catch (IOException exception)
{
// JmDNS never throws this is only because of the closeable interface
}
}
});
}
}
finally
{
executor.shutdown();
}
try
{
executor.awaitTermination(DNSConstants.CLOSE_TIMEOUT,
TimeUnit.MILLISECONDS);
}
catch (InterruptedException exception)
{
logger.log(Level.WARNING, "Exception ", exception);
}
_knownMDNS.clear();
_services.clear();
_serviceListeners.clear();
_typeListeners.clear();
_serviceTypes.clear();
_closed.set(true);
JmmDNS.Factory.close();
}
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#getNames()
*/
@Override
public String[] getNames()
{
Set<String> result = new HashSet<String>();
for (JmDNS mDNS : this.getDNS())
{
result.add(mDNS.getName());
}
return result.toArray(new String[result.size()]);
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#getHostNames()
*/
@Override
public String[] getHostNames()
{
Set<String> result = new HashSet<String>();
for (JmDNS mDNS : this.getDNS())
{
result.add(mDNS.getHostName());
}
return result.toArray(new String[result.size()]);
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#getInetAddresses()
*/
@Override
public InetAddress[] getInetAddresses() throws IOException
{
Set<InetAddress> result = new HashSet<InetAddress>();
for (JmDNS mDNS : this.getDNS())
{
result.add(mDNS.getInetAddress());
}
return result.toArray(new InetAddress[result.size()]);
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#getDNS()
*/
@Override
public JmDNS[] getDNS()
{
synchronized (_knownMDNS)
{
return _knownMDNS.values().toArray(new JmDNS[_knownMDNS.size()]);
}
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#getInterfaces()
*/
@Override
@Deprecated
public InetAddress[] getInterfaces() throws IOException
{
Set<InetAddress> result = new HashSet<InetAddress>();
for (JmDNS mDNS : this.getDNS())
{
result.add(mDNS.getInterface());
}
return result.toArray(new InetAddress[result.size()]);
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#getServiceInfos(java.lang.String,
* java.lang.String)
*/
@Override
public ServiceInfo[] getServiceInfos(String type, String name)
{
return this.getServiceInfos(type, name, false, DNSConstants.SERVICE_INFO_TIMEOUT);
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#getServiceInfos(java.lang.String,
* java.lang.String, long)
*/
@Override
public ServiceInfo[] getServiceInfos(String type, String name, long timeout)
{
return this.getServiceInfos(type, name, false, timeout);
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#getServiceInfos(java.lang.String,
* java.lang.String, boolean)
*/
@Override
public ServiceInfo[] getServiceInfos(String type, String name, boolean persistent)
{
return this.getServiceInfos(type, name, persistent,
DNSConstants.SERVICE_INFO_TIMEOUT);
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#getServiceInfos(java.lang.String,
* java.lang.String, boolean, long)
*/
@Override
public ServiceInfo[] getServiceInfos(final String type, final String name,
final boolean persistent, final long timeout)
{
// We need to run this in parallel to respect the timeout.
final JmDNS[] dnsArray = this.getDNS();
final Set<ServiceInfo> result = new HashSet<ServiceInfo>(dnsArray.length);
if (dnsArray.length > 0)
{
List<Callable<ServiceInfo>> tasks = new ArrayList<Callable<ServiceInfo>>(
dnsArray.length);
for (final JmDNS mDNS : dnsArray)
{
tasks.add(new Callable<ServiceInfo>()
{
@Override
public ServiceInfo call() throws Exception
{
return mDNS.getServiceInfo(type, name, persistent, timeout);
}
});
}
ExecutorService executor = Executors.newFixedThreadPool(tasks.size(),
new NamedThreadFactory("JmmDNS.getServiceInfos"));
try
{
List<Future<ServiceInfo>> results = Collections.emptyList();
try
{
results = executor.invokeAll(tasks, timeout + 100,
TimeUnit.MILLISECONDS);
}
catch (InterruptedException exception)
{
logger.log(Level.FINE, "Interrupted ", exception);
Thread.currentThread().interrupt();
// Will terminate next loop early.
}
for (Future<ServiceInfo> future : results)
{
if (future.isCancelled())
{
continue;
}
try
{
ServiceInfo info = future.get();
if (info != null)
{
result.add(info);
}
}
catch (InterruptedException exception)
{
logger.log(Level.FINE, "Interrupted ", exception);
Thread.currentThread().interrupt();
}
catch (ExecutionException exception)
{
logger.log(Level.WARNING, "Exception ", exception);
}
}
}
finally
{
executor.shutdown();
}
}
return result.toArray(new ServiceInfo[result.size()]);
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#requestServiceInfo(java.lang.String,
* java.lang.String)
*/
@Override
public void requestServiceInfo(String type, String name)
{
this.requestServiceInfo(type, name, false, DNSConstants.SERVICE_INFO_TIMEOUT);
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#requestServiceInfo(java.lang.String,
* java.lang.String, boolean)
*/
@Override
public void requestServiceInfo(String type, String name, boolean persistent)
{
this.requestServiceInfo(type, name, persistent, DNSConstants.SERVICE_INFO_TIMEOUT);
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#requestServiceInfo(java.lang.String,
* java.lang.String, long)
*/
@Override
public void requestServiceInfo(String type, String name, long timeout)
{
this.requestServiceInfo(type, name, false, timeout);
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#requestServiceInfo(java.lang.String,
* java.lang.String, boolean, long)
*/
@Override
public void requestServiceInfo(final String type, final String name,
final boolean persistent, final long timeout)
{
// We need to run this in parallel to respect the timeout.
for (final JmDNS mDNS : this.getDNS())
{
_jmDNSExecutor.submit(new Runnable()
{
/**
* {@inheritDoc}
*/
@Override
public void run()
{
mDNS.requestServiceInfo(type, name, persistent, timeout);
}
});
}
}
/*
* (non-Javadoc)
*
* @see
* javax.jmdns.JmmDNS#addServiceTypeListener(javax.jmdns.ServiceTypeListener
* )
*/
@Override
public void addServiceTypeListener(ServiceTypeListener listener) throws IOException
{
_typeListeners.add(listener);
for (JmDNS mDNS : this.getDNS())
{
mDNS.addServiceTypeListener(listener);
}
}
/*
* (non-Javadoc)
*
* @see
* javax.jmdns.JmmDNS#removeServiceTypeListener(javax.jmdns.ServiceTypeListener
* )
*/
@Override
public void removeServiceTypeListener(ServiceTypeListener listener)
{
_typeListeners.remove(listener);
for (JmDNS mDNS : this.getDNS())
{
mDNS.removeServiceTypeListener(listener);
}
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#addServiceListener(java.lang.String,
* javax.jmdns.ServiceListener)
*/
@Override
public void addServiceListener(String type, ServiceListener listener)
{
final String loType = type.toLowerCase();
List<ServiceListener> list = _serviceListeners.get(loType);
if (list == null)
{
_serviceListeners.putIfAbsent(loType, new LinkedList<ServiceListener>());
list = _serviceListeners.get(loType);
}
if (list != null)
{
synchronized (list)
{
if (!list.contains(listener))
{
list.add(listener);
}
}
}
for (JmDNS mDNS : this.getDNS())
{
mDNS.addServiceListener(type, listener);
}
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#removeServiceListener(java.lang.String,
* javax.jmdns.ServiceListener)
*/
@Override
public void removeServiceListener(String type, ServiceListener listener)
{
String loType = type.toLowerCase();
List<ServiceListener> list = _serviceListeners.get(loType);
if (list != null)
{
synchronized (list)
{
list.remove(listener);
if (list.isEmpty())
{
_serviceListeners.remove(loType, list);
}
}
}
for (JmDNS mDNS : this.getDNS())
{
mDNS.removeServiceListener(type, listener);
}
}
/*
* (non-Javadoc)
*
* @see
* javax.jmdns.impl.ServiceInfoImpl.Delegate#textValueUpdated(javax.jmdns
* .ServiceInfo, byte[])
*/
@Override
public void textValueUpdated(ServiceInfo target, byte[] value)
{
// We need to get the list out of the synchronized block to prevent dead locks
final JmDNS[] dnsArray = this.getDNS();
synchronized (_services)
{
for (JmDNS mDNS : dnsArray)
{
ServiceInfo info = ((JmDNSImpl) mDNS).getServices().get(
target.getQualifiedName());
if (info != null)
{
info.setText(value);
}
else
{
logger.warning("We have a mDNS that does not know about the service info being updated.");
}
}
}
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#registerService(javax.jmdns.ServiceInfo)
*/
@Override
public void registerService(ServiceInfo info) throws IOException
{
// We need to get the list out of the synchronized block to prevent dead locks
final JmDNS[] dnsArray = this.getDNS();
// This is really complex. We need to clone the service info for each DNS but then we loose the ability to update it.
synchronized (_services)
{
for (JmDNS mDNS : dnsArray)
{
mDNS.registerService(info.clone());
}
((ServiceInfoImpl) info).setDelegate(this);
_services.put(info.getQualifiedName(), info);
}
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#unregisterService(javax.jmdns.ServiceInfo)
*/
@Override
public void unregisterService(ServiceInfo info)
{
// We need to get the list out of the synchronized block to prevent dead locks
final JmDNS[] dnsArray = this.getDNS();
synchronized (_services)
{
_services.remove(info.getQualifiedName());
for (JmDNS mDNS : dnsArray)
{
mDNS.unregisterService(info);
}
((ServiceInfoImpl) info).setDelegate(null);
}
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#unregisterAllServices()
*/
@Override
public void unregisterAllServices()
{
// We need to get the list out of the synchronized block to prevent dead locks
final JmDNS[] dnsArray = this.getDNS();
synchronized (_services)
{
_services.clear();
for (JmDNS mDNS : dnsArray)
{
mDNS.unregisterAllServices();
}
}
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#registerServiceType(java.lang.String)
*/
@Override
public void registerServiceType(String type)
{
_serviceTypes.add(type);
for (JmDNS mDNS : this.getDNS())
{
mDNS.registerServiceType(type);
}
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#list(java.lang.String)
*/
@Override
public ServiceInfo[] list(String type)
{
return this.list(type, DNSConstants.SERVICE_INFO_TIMEOUT);
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#list(java.lang.String, long)
*/
@Override
public ServiceInfo[] list(final String type, final long timeout)
{
final JmDNS[] dnsArray = this.getDNS();
// We need to run this in parallel to respect the timeout.
final Set<ServiceInfo> result = new HashSet<ServiceInfo>(dnsArray.length * 5);
if (dnsArray.length > 0)
{
List<Callable<List<ServiceInfo>>> tasks = new ArrayList<Callable<List<ServiceInfo>>>(
dnsArray.length);
for (final JmDNS mDNS : dnsArray)
{
tasks.add(new Callable<List<ServiceInfo>>()
{
@Override
public List<ServiceInfo> call() throws Exception
{
return Arrays.asList(mDNS.list(type, timeout));
}
});
}
ExecutorService executor = Executors.newFixedThreadPool(tasks.size(),
new NamedThreadFactory("JmmDNS.list"));
try
{
List<Future<List<ServiceInfo>>> results = Collections.emptyList();
try
{
results = executor.invokeAll(tasks, timeout + 100,
TimeUnit.MILLISECONDS);
}
catch (InterruptedException exception)
{
logger.log(Level.FINE, "Interrupted ", exception);
Thread.currentThread().interrupt();
// Will terminate next loop early.
}
for (Future<List<ServiceInfo>> future : results)
{
if (future.isCancelled())
{
continue;
}
try
{
result.addAll(future.get());
}
catch (InterruptedException exception)
{
logger.log(Level.FINE, "Interrupted ", exception);
Thread.currentThread().interrupt();
}
catch (ExecutionException exception)
{
logger.log(Level.WARNING, "Exception ", exception);
}
}
}
finally
{
executor.shutdown();
}
}
return result.toArray(new ServiceInfo[result.size()]);
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#listBySubtype(java.lang.String)
*/
@Override
public Map<String, ServiceInfo[]> listBySubtype(String type)
{
return this.listBySubtype(type, DNSConstants.SERVICE_INFO_TIMEOUT);
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#listBySubtype(java.lang.String, long)
*/
@Override
public Map<String, ServiceInfo[]> listBySubtype(final String type, final long timeout)
{
Map<String, List<ServiceInfo>> map = new HashMap<String, List<ServiceInfo>>(5);
for (ServiceInfo info : this.list(type, timeout))
{
String subtype = info.getSubtype();
if (!map.containsKey(subtype))
{
map.put(subtype, new ArrayList<ServiceInfo>(10));
}
map.get(subtype).add(info);
}
Map<String, ServiceInfo[]> result = new HashMap<String, ServiceInfo[]>(map.size());
for (String subtype : map.keySet())
{
List<ServiceInfo> infoForSubType = map.get(subtype);
result.put(subtype,
infoForSubType.toArray(new ServiceInfo[infoForSubType.size()]));
}
return result;
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#addNetworkTopologyListener(javax.jmdns.
* NetworkTopologyListener)
*/
@Override
public void addNetworkTopologyListener(NetworkTopologyListener listener)
{
_networkListeners.add(listener);
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#removeNetworkTopologyListener(javax.jmdns.
* NetworkTopologyListener)
*/
@Override
public void removeNetworkTopologyListener(NetworkTopologyListener listener)
{
_networkListeners.remove(listener);
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.JmmDNS#networkListeners()
*/
@Override
public NetworkTopologyListener[] networkListeners()
{
return _networkListeners.toArray(new NetworkTopologyListener[_networkListeners
.size()]);
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.NetworkTopologyListener#inetAddressAdded(javax.jmdns.
* NetworkTopologyEvent)
*/
@Override
public void inetAddressAdded(NetworkTopologyEvent event)
{
InetAddress address = event.getInetAddress();
try
{
if (!_knownMDNS.containsKey(address))
{
synchronized (_knownMDNS)
{
if (!_knownMDNS.containsKey(address))
{
final JmDNS dns = JmDNS.create(address);
if (_knownMDNS.putIfAbsent(address, dns) == null)
{
// We need to register the services and listeners with the new JmDNS
final Collection<String> types = _serviceTypes;
final Collection<ServiceInfo> infos = _services.values();
final Collection<ServiceTypeListener> typeListeners = _typeListeners;
final Map<String, List<ServiceListener>> serviceListeners = _serviceListeners;
_jmDNSExecutor.submit(new Runnable()
{
/**
* {@inheritDoc}
*/
@Override
public void run()
{
// Register Types
for (String type : types)
{
dns.registerServiceType(type);
}
// Register services
for (ServiceInfo info : infos)
{
try
{
dns.registerService(info.clone());
}
catch (IOException exception)
{
// logger.warning("Unexpected unhandled exception: " + exception);
}
}
// Add ServiceType Listeners
for (ServiceTypeListener listener : typeListeners)
{
try
{
dns.addServiceTypeListener(listener);
}
catch (IOException exception)
{
// logger.warning("Unexpected unhandled exception: " + exception);
}
}
// Add Service Listeners
for (String type : serviceListeners.keySet())
{
List<ServiceListener> listeners = serviceListeners
.get(type);
synchronized (listeners)
{
for (ServiceListener listener : listeners)
{
dns.addServiceListener(type, listener);
}
}
}
}
});
final NetworkTopologyEvent jmdnsEvent = new NetworkTopologyEventImpl(
dns, address);
for (final NetworkTopologyListener listener : this
.networkListeners())
{
_listenerExecutor.submit(new Runnable()
{
/**
* {@inheritDoc}
*/
@Override
public void run()
{
listener.inetAddressAdded(jmdnsEvent);
}
});
}
}
else
{
dns.close();
}
}
}
}
}
catch (Exception e)
{
logger.warning("Unexpected unhandled exception: " + e);
}
}
/*
* (non-Javadoc)
*
* @see javax.jmdns.NetworkTopologyListener#inetAddressRemoved(javax.jmdns.
* NetworkTopologyEvent)
*/
@Override
public void inetAddressRemoved(NetworkTopologyEvent event)
{
InetAddress address = event.getInetAddress();
try
{
if (_knownMDNS.containsKey(address))
{
synchronized (_knownMDNS)
{
if (_knownMDNS.containsKey(address))
{
JmDNS mDNS = _knownMDNS.remove(address);
mDNS.close();
final NetworkTopologyEvent jmdnsEvent = new NetworkTopologyEventImpl(
mDNS, address);
for (final NetworkTopologyListener listener : this
.networkListeners())
{
_listenerExecutor.submit(new Runnable()
{
/**
* {@inheritDoc}
*/
@Override
public void run()
{
listener.inetAddressRemoved(jmdnsEvent);
}
});
}
}
}
}
}
catch (Exception e)
{
logger.warning("Unexpected unhandled exception: " + e);
}
}
/**
* Checks the network state.<br/>
* If the network change, this class will reconfigure the list of DNS do
* adapt to the new configuration.
*/
static class NetworkChecker extends TimerTask
{
private static Logger logger1 = Logger.getLogger(NetworkChecker.class.getName());
private final NetworkTopologyListener _mmDNS;
private final NetworkTopologyDiscovery _topology;
private Set<InetAddress> _knownAddresses;
public NetworkChecker(NetworkTopologyListener mmDNS,
NetworkTopologyDiscovery topology)
{
super();
this._mmDNS = mmDNS;
this._topology = topology;
_knownAddresses = Collections.synchronizedSet(new HashSet<InetAddress>());
}
public void start(Timer timer)
{
// Run once up-front otherwise the list of servers will only appear after a delay.
run();
timer.schedule(this, DNSConstants.NETWORK_CHECK_INTERVAL,
DNSConstants.NETWORK_CHECK_INTERVAL);
}
/**
* {@inheritDoc}
*/
@Override
public void run()
{
try
{
InetAddress[] curentAddresses = _topology.getInetAddresses();
Set<InetAddress> current = new HashSet<InetAddress>(
curentAddresses.length);
for (InetAddress address : curentAddresses)
{
current.add(address);
if (!_knownAddresses.contains(address))
{
final NetworkTopologyEvent event = new NetworkTopologyEventImpl(
_mmDNS, address);
_mmDNS.inetAddressAdded(event);
}
}
for (InetAddress address : _knownAddresses)
{
if (!current.contains(address))
{
final NetworkTopologyEvent event = new NetworkTopologyEventImpl(
_mmDNS, address);
_mmDNS.inetAddressRemoved(event);
}
}
_knownAddresses = current;
}
catch (Exception e)
{
logger1.warning("Unexpected unhandled exception: " + e);
}
}
}
}