Package net.xeoh.plugins.remotediscovery.impl.v3

Source Code of net.xeoh.plugins.remotediscovery.impl.v3.RemoteDiscoveryImpl

package net.xeoh.plugins.remotediscovery.impl.v3;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Logger;

import javax.jmdns.JmDNS;
import javax.jmdns.ServiceInfo;
import javax.jmdns.impl.tasks.ServiceResolver;

import net.xeoh.plugins.base.Plugin;
import net.xeoh.plugins.base.PluginConfiguration;
import net.xeoh.plugins.base.PluginInformation;
import net.xeoh.plugins.base.annotations.PluginImplementation;
import net.xeoh.plugins.base.annotations.Thread;
import net.xeoh.plugins.base.annotations.configuration.IsDisabled;
import net.xeoh.plugins.base.annotations.events.Shutdown;
import net.xeoh.plugins.base.annotations.injections.InjectPlugin;
import net.xeoh.plugins.base.util.OptionUtils;
import net.xeoh.plugins.base.util.PluginConfigurationUtil;
import net.xeoh.plugins.remote.PublishMethod;
import net.xeoh.plugins.remotediscovery.DiscoveredPlugin;
import net.xeoh.plugins.remotediscovery.RemoteDiscovery;
import net.xeoh.plugins.remotediscovery.impl.common.discoverymanager.DiscoveryManager;
import net.xeoh.plugins.remotediscovery.impl.common.discoverymanager.ExportInfo;
import net.xeoh.plugins.remotediscovery.impl.common.discoverymanager.ExportedPlugin;
import net.xeoh.plugins.remotediscovery.impl.common.discoverymanager.impl.tcpip.DiscoveryManagerTCPIPImpl;
import net.xeoh.plugins.remotediscovery.options.DiscoverOption;
import net.xeoh.plugins.remotediscovery.options.discover.OptionCallback;
import net.xeoh.plugins.remotediscovery.options.discover.OptionNearest;
import net.xeoh.plugins.remotediscovery.options.discover.OptionOldest;
import net.xeoh.plugins.remotediscovery.options.discover.OptionYoungest;

import org.freshvanilla.rmi.Proxies;
import org.freshvanilla.rmi.VanillaRmiServer;
import org.freshvanilla.utils.SimpleResource;

/**
* @author Thomas Lottermann
*
*/
@IsDisabled
@PluginImplementation
public class RemoteDiscoveryImpl implements RemoteDiscovery {

    private static final String EXPORT_NAME = "DiscoveryManager";

    private static final String NAME = "JSPF";

    private static final String TYPE = "_jspfplugindiscovery._tcp.local.";

    /**
     * @return
     */
    private static int getFreePort() {

        try {
            final ServerSocket serverSocket = new ServerSocket(0);
            final int port = serverSocket.getLocalPort();
            serverSocket.close();
            return port;
        } catch (IOException e) {
            e.printStackTrace();
        }

        return 1025 + new Random().nextInt(50000);
    }

    /** */
    @InjectPlugin
    public PluginConfiguration pluginConfiguration;

    /** */
    @InjectPlugin
    public PluginInformation pluginInformation;

    /** How we lock for the first call? */
    private String lockMode;

    private int startupLock;

    /** All callbacks for discovered plugins */
    protected final List<CallbackRequest> allRequests = new ArrayList<CallbackRequest>();

    protected final Lock jmdnsLock = new ReentrantLock();

    /** The local manager */
    protected final DiscoveryManagerTCPIPImpl localManager = new DiscoveryManagerTCPIPImpl();

    /**
     * Contains a list of remote DiscoveryManagers all over the network and also
     * the local manager
     */
    protected final HashSet<RemoteManagerEndpoint> remoteManagersEndpoints = new HashSet<RemoteManagerEndpoint>();

    /** usually should contain only all DiscoveryManager; */
    protected final Collection<ServiceInfo> serviceInfos = new ArrayList<ServiceInfo>();

    /** */
    protected final Lock serviceInfosLock = new ReentrantLock();

    protected final CountDownLatch startupLatch = new CountDownLatch(1);

    final CheckCacheCall checkCache = new CheckCacheCall(this);

    final DiscoverCall discover = new DiscoverCall(this);

    /** */
    AtomicLong discoverThreadCounter = new AtomicLong();

    JmDNS jmdns;

    /** Server of the exported local manager */
    VanillaRmiServer<DiscoveryManager> localManagerExportServer;

    /** */
    final Logger logger = Logger.getLogger(this.getClass().getName());

    /**
     * Constructs daemonic threads
     */
    ThreadFactory threadFactory = new ThreadFactory() {

        public java.lang.Thread newThread(Runnable r) {
            java.lang.Thread rval = new java.lang.Thread(r);
            rval.setDaemon(true);
            return rval;
        }
    };

    long timeOfStartup;

    /*
     * (non-Javadoc)
     *
     * @see
     * net.xeoh.plugins.remotediscovery.RemoteDiscovery#announcePlugin(net.xeoh
     * .plugins.base.Plugin, net.xeoh.plugins.remote.PublishMethod,
     * java.net.URI)
     */
    public void announcePlugin(Plugin plugin, PublishMethod publishMethod, URI uri) {
        this.localManager.anouncePlugin(plugin, publishMethod, uri);
    }

    /** */
    @SuppressWarnings("boxing")
    @Thread(isDaemonic = true)
    public void backgroundInit() {
        // (Ralf:) zomfg, this is one of the worst hacks i've done the last
        // couple of months. Deep within jmdns we placed a variable to
        // override a check if it is already save to transmit something. jmDNS
        // usually takes 5 seconds to reach that state, but that's
        // too long for us. If you set this variable the check will be skipped
        // and the request should take place much faster.
        // Appears to work(tm).
        ServiceResolver.ANNOUNCE_OVERRIDE = true;

        final PluginConfigurationUtil pcu = new PluginConfigurationUtil(this.pluginConfiguration);
        this.startupLock = pcu.getInt(RemoteDiscovery.class, "startup.locktime", 1000);
        this.lockMode = pcu.getString(RemoteDiscovery.class, "startup.lockmode", "onepass");

        // TODO put this in a thread
        this.checkCache.loadCache();

        try {
            this.jmdnsLock.lock();
            this.jmdns = JmDNS.create(); // Maybe init with local loopback in case no other network card is present, otherwise returns null

            this.timeOfStartup = System.currentTimeMillis();

            final int port = getFreePort();
            final ServiceInfo service = ServiceInfo.create(TYPE, NAME + " @" + this.timeOfStartup, port, 0, 0, EXPORT_NAME);

            this.localManagerExportServer = Proxies.newServer(EXPORT_NAME, port, (DiscoveryManager) this.localManager);
            this.jmdns.registerService(service);

            // Set it again, this time for the lock below
            this.timeOfStartup = System.currentTimeMillis();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            this.logger.warning("Error starting discovery.");
        } finally {
            this.startupLatch.countDown();
            this.jmdnsLock.unlock();
        }

        // and load our cache...
        // todo make this configurable...
        //    this.checkCache.loadCache(fileName)
    }

    /* (non-Javadoc)
     * @see net.xeoh.plugins.remotediscovery.RemoteDiscovery#discover(java.lang.Class, net.xeoh.plugins.remotediscovery.options.DiscoverOption[])
     */
    public Collection<DiscoveredPlugin> discover(Class<? extends Plugin> plugin,
                                                 DiscoverOption... options) {

        refreshKnownDiscoveryManagers();

        //* new 2
        final CompletionService<Collection<DiscoveredPlugin>> compService = new ExecutorCompletionService<Collection<DiscoveredPlugin>>(Executors.newFixedThreadPool(2, this.threadFactory));

        this.checkCache.initCall(plugin, options);
        this.discover.initCall(plugin, options);

        List<Future<Collection<DiscoveredPlugin>>> futures = new ArrayList<Future<Collection<DiscoveredPlugin>>>();
        futures.add(compService.submit(this.checkCache));
        futures.add(compService.submit(this.discover));

        Future<Collection<DiscoveredPlugin>> completedFuture;
        Collection<DiscoveredPlugin> foundPlugins = null;

        while (!futures.isEmpty()) {
            try {
                completedFuture = compService.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
                return null;
            }

            futures.remove(completedFuture);

            try {
                foundPlugins = completedFuture.get();

                if (foundPlugins != null && !foundPlugins.isEmpty() && !futures.isEmpty()) {
                    // so we found something, just remote the other one...
                    futures.get(0).cancel(true);
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }

        return foundPlugins;
    }

    /** */
    @net.xeoh.plugins.base.annotations.Timer(period = 260)
    public void discoverThread() {

        this.logger.fine("Starting new discover pass");

        try {
            this.logger.finer("Awaiting latch");
            this.startupLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
            return;
        }

        this.logger.finer("Latch passed");

        // Increase counter
        this.discoverThreadCounter.incrementAndGet();

        // Create empty data.
        ServiceInfo[] infos = new ServiceInfo[0];

        // Magic: Get all network services of our type.
        try {
            this.logger.finer("Trying to lock jmDNS lock");
            this.jmdnsLock.lock();
            this.logger.finer("Lock obtained. Listing known entries of our type");
            // TODO: jmdsn can be null if no network card is present
            infos = this.jmdns.list(TYPE);
            this.logger.finer("List obtained.");
        } catch (IllegalStateException e) {
            this.logger.warning("Error discovering plugins ...");
        } finally {
            this.jmdnsLock.unlock();
            this.logger.finer("Lock unlocked.");
        }

        // Reset all service infos
        try {
            this.logger.finer("Trying to obtain service info lock.");
            this.serviceInfosLock.lock();
            this.logger.finer("Service info lock obtained. Transferring data.");
            this.serviceInfos.clear();
            this.serviceInfos.addAll(Arrays.asList(infos));

            this.logger.finer("Data transferred.");
        } finally {
            this.serviceInfosLock.unlock();
            this.logger.finer("Service info unlocked.");
        }

        // Process all callbacks.
        final Collection<CallbackRequest> toRemove = new ArrayList<CallbackRequest>();

        //
        // TODO: This callback handling needs improvement. Check for
        // unsynchronized access to the allRequest structure
        //

        // Check all callbacks, if they need, well, a callback
        for (CallbackRequest cr : this.allRequests) {
            final Collection<DiscoveredPlugin> found = resolve(cr.req, cr.moreOptions);
            if (found.size() > 0) {
                cr.oc.getCallback().pluginsDiscovered(found);
                toRemove.add(cr);
            }
        }

        this.logger.finer("Callbacks executed. Removing items.");

        // Remove all resolved callbacks
        for (CallbackRequest callbackRequest : toRemove) {
            callbackRequest.cancelTimeout();
            this.allRequests.remove(callbackRequest);
        }

        this.logger.fine("Disover pass ended.");
    }

    /* (non-Javadoc)
     * @see net.xeoh.plugins.remotediscovery.RemoteDiscovery#revokePlugin(net.xeoh.plugins.base.Plugin, net.xeoh.plugins.remote.PublishMethod, java.net.URI)
     */
    public void revokePlugin(Plugin plugin, PublishMethod publishMethod, URI uri) {
        // TODO Auto-generated method stub     
    }

    /** */
    @Shutdown
    public void shutdown() {
        final java.lang.Thread t = new java.lang.Thread(new Runnable() {

            public void run() {
                RemoteDiscoveryImpl.this.jmdnsLock.lock();

                // All of these statements tend to fail because of various reasons. During the shutdown however,
                // we want to ignore them all ...
                try {
                    try {
                        RemoteDiscoveryImpl.this.localManagerExportServer.close();
                    } catch (Exception e) {
                        //
                    }
                    try {
                        RemoteDiscoveryImpl.this.jmdns.unregisterAllServices();
                    } catch (Exception e) {
                        //
                    }
                    try {
                        RemoteDiscoveryImpl.this.jmdns.close();
                    } catch (Exception e) {
                        //
                    }
                } finally {
                    RemoteDiscoveryImpl.this.jmdnsLock.unlock();
                }
            }
        });
        t.setDaemon(true);
        t.start();

        //    this.checkCache.printCache();
        this.checkCache.saveCache();
    }

    private boolean awaitInitializationTime() {
        // Check if we're still on startup lock
        final long delay = this.timeOfStartup + this.startupLock - System.currentTimeMillis();
        if (delay > 0) {
            // If we are, halt this thread until lock time has passed.
            // Unfortunatly the lookup is a
            // bit "unstable" during the first few miliseconds.
            try {
                java.lang.Thread.sleep(delay);
            } catch (InterruptedException e) {
                e.printStackTrace();
                return false;
            }
        }

        return true;
    }

    /**
     * Returns the remote proxy to a discovery manager for a given IP / port
     *
     * @param ip
     * @param port
     * @return
     */
    @SuppressWarnings("unchecked")
    DiscoveryManager getRemoteProxy(String ip, int port) {
        try {

            final String address = ip + ":" + port;
            final int timeout = 500; // 500 ms RemoteAPIImpl if need detailed
            // version...

            final DiscoveryManager newClient = Proxies.newClient(EXPORT_NAME, address, RemoteDiscoveryImpl.class.getClassLoader(), DiscoveryManager.class);

            /*
             * TODO: With this one we still get the message:
             *
             * "org.freshvanilla.net.VanillaDataSocket allocateBuffer
             * INFO: DiscoveryManager: Running low on memory, pausing..."
             *
             * but, there is no TimeoutException....
             *
             * @sa DiscoveredPluginTest.testMultiplePlugins()
             */
            @SuppressWarnings("unused")
            int pingRes = newClient.ping(123321);

            // Execute collection asynchronously (TODO: cache pool usage could be
            // improved)
            final ExecutorService cachePool = Executors.newCachedThreadPool();
            final ExecutorCompletionService<String> ecs = new ExecutorCompletionService(cachePool);
            final Future<String> future = ecs.submit(new Callable<String>() {

                public String call() throws Exception {
                    return AccessController.doPrivileged(new PrivilegedAction<String>() {
                        public String run() {
                            return newClient.toString();
                        }
                    });
                }
            });

            // Wait at most half a second (TODO: Make this configurable)
            try {
                final String string = future.get(timeout, TimeUnit.MILLISECONDS);
                /*
                 * TODO: Probably it is possible to put here some conversion routines... Because it looks like that
                 * only the ExportInfo makes trouble.... Or just make the ExportInfo much better, ie downwards compatible
                 */
                if (string == null || newClient.getVersion() != this.localManager.getVersion()) { return null; }

                return newClient;
            } catch (final InterruptedException e) {
                // TODO: This one is not an error anymore.
                // Interruption is called because we are running cache thread.
                //        System.err
                //            .println("Error while waiting for a getRemoteProxy() result");
                //        e.printStackTrace();
            } catch (final ExecutionException e) {
                e.printStackTrace();
            } catch (final TimeoutException e) {
                e.printStackTrace();
            } catch (final SecurityException e) {
                e.printStackTrace();
            } finally {
                AccessController.doPrivileged(new PrivilegedAction<Object>() {
                    public Object run() {
                        future.cancel(true);
                        cachePool.shutdownNow();
                        return null;
                    }
                });
            }
            return null;
        } finally {
        }
    }

    /**
     * Refreshes all known discovery managers.
     */
    private void refreshKnownDiscoveryManagers() {

        this.serviceInfosLock.lock();
        try {
            this.remoteManagersEndpoints.clear();

            // Check all service infos with discovery managers
            for (ServiceInfo serviceInfo : this.serviceInfos) {

                final InetAddress address = serviceInfo.getAddress();
                final int port = serviceInfo.getPort();

                System.out.println("mgr: " + address + " " + port);
                this.remoteManagersEndpoints.add(new RemoteManagerEndpoint(address, port));
            }
        } finally {
            this.serviceInfosLock.unlock();
        }
    }

    /**
     * Checks all known service infos for matches to the given class, returns
     * all matching entries.
     *
     * @param plugin
     * @param options
     * @return
     */
    private Collection<DiscoveredPlugin> resolve(final Class<? extends Plugin> plugin,
                                                 DiscoverOption[] options) {

        // Refreshes all known remote discovery managers.
        //        refreshKnownDiscoveryManagers();

        // The result we intend to return
        Collection<DiscoveredPlugin> result = new ArrayList<DiscoveredPlugin>(); // new ArrayList<DiscoveredPlugin>();

        // Query all managers
        for (final RemoteManagerEndpoint endpoint : this.remoteManagersEndpoints) {

            // The manager to use ...
            final DiscoveryManager mgr = RemoteDiscoveryImpl.this.getRemoteProxy(endpoint.address.getHostAddress(), endpoint.port);

            // No matter what happens, close the manager
            try {
                if (mgr == null) {
                    this.logger.info("Remote DiscoveryManager at " + endpoint.address.getHostAddress() + ":" + endpoint.port + " did not answer or has not the right version.");
                    continue;
                }

                // TODO: here are probably not the complete information as it was before....
                // RemoteDiscoveryImpl.this.logger.info("Error getting ping time of remote manager " +
                //          endpoint.address + ":" + endpoint.port);
                final AtomicInteger pingTime = getPingTime(mgr, endpoint.address.getHostAddress(), endpoint.port);

                // Query the information (needs to be inside a privileged block, otherwise applets might complain.
                final ExportInfo exportInfo = AccessController.doPrivileged(new PrivilegedAction<ExportInfo>() {
                    public ExportInfo run() {
                        return mgr.getExportInfoFor(plugin.getCanonicalName());
                    }
                });

                // If the plugin in not exported, do nothing
                if (!exportInfo.isExported) continue;

                // If it is, construct required data.
                String hostAddress = (endpoint.address != null) ? endpoint.address.getHostAddress() : "127.0.0.1";
                DiscoveredPlugin discPlugin = constructPlugins(hostAddress, exportInfo.allExported, pingTime.get());
                this.checkCache.addToCache(plugin, endpoint, discPlugin);
                result.add(discPlugin);
            } catch (ClassCastException e) {
                // This should not be happen anymore....
                this.logger.warning(" Trying to convert a object from v2 to v3.");
                e.printStackTrace();

            } finally {
                // In case the manager is of the type simple resource (which it should be), try to close it.
                if (mgr instanceof SimpleResource) {
                    try {

                        // Try to close our device again
                        AccessController.doPrivileged(new PrivilegedAction<Object>() {
                            public Object run() {
                                ((SimpleResource) mgr).close();
                                return null;
                            }
                        });
                    } catch (Exception e) {
                        e.printStackTrace();
                        this.logger.fine("Error closing remote DiscoveryManager ...");
                    }
                }
            }
        }

        // If we have no result there is nothing to do
        if (result.size() == 0) { return new ArrayList<DiscoveredPlugin>(); }

        //        doFilter(result, options);

        return result;
    }

    final DiscoveredPlugin constructPlugins(final String hostAddress,
                                            final Collection<ExportedPlugin> plugins,
                                            final int pingTime) {
        // If it is, construct required data.
        for (ExportedPlugin p : plugins) {
            final PublishMethod method = PublishMethod.valueOf(p.exportMethod);
            final URI uri = p.exportURI;

            String _newURI = "";

            _newURI += uri.getScheme();
            _newURI += "://";
            _newURI += hostAddress;
            _newURI += ":";
            _newURI += uri.getPort();
            _newURI += uri.getPath();

            try {
                // TODO: Compute distance properly.
                return new DiscoveredPluginImpl(method, new URI(_newURI), pingTime, p.timeSinceExport);
            } catch (URISyntaxException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    final void doFilter(Collection<DiscoveredPlugin> plugins, DiscoverOption[] options) {
        final OptionUtils<DiscoverOption> ou = new OptionUtils<DiscoverOption>(options);

        DiscoveredPlugin best = plugins.iterator().next();

        if (ou.contains(OptionNearest.class)) {
            // Check all plugins
            for (DiscoveredPlugin p : plugins) {
                // If this one is closer, replace them
                if (p.getDistance() < best.getDistance()) {
                    best = p;
                }
            }

            // Remove all other plugins
            plugins.clear();
            plugins.add(best);
        }

        if (ou.contains(OptionYoungest.class)) {
            // Check all plugins
            for (DiscoveredPlugin p : plugins) {
                // If this one is closer, replace them
                if (p.getTimeSinceExport() < best.getTimeSinceExport()) {
                    best = p;
                }
            }

            // Remove all other plugins
            plugins.clear();
            plugins.add(best);
        }

        if (ou.contains(OptionOldest.class)) {
            // Check all plugins
            for (DiscoveredPlugin p : plugins) {
                // If this one is closer, replace them
                if (p.getTimeSinceExport() > best.getTimeSinceExport()) {
                    best = p;
                }
            }

            // Remove all other plugins
            plugins.clear();
            plugins.add(best);
        }
    }

    /*
     * we are passing the InterruptedException, because this fct can be interrupted by cache-thread.
     */
    Collection<DiscoveredPlugin> doRemoteDiscover(Class<? extends Plugin> plugin,
                                                  DiscoverOption... options)
                                                                            throws InterruptedException {
        // Wait until init is done ...
        try {
            this.startupLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
            return new ArrayList<DiscoveredPlugin>();
        }

        // Timelock: Wait for a certain time to pass since startup
        if (this.lockMode.equals("timelock")) {
            if (awaitInitializationTime() == false) { return new ArrayList<DiscoveredPlugin>(); }
        }

        // Onepass: Wait for the discover to execute once
        if (this.lockMode.equals("onepass")) {

            // Also wait some time here, otherwise we won't find all plugins in
            // all cases
            awaitInitializationTime();

            // Wait at least one discoverThreadTurn
            final long lastDisoverValue = this.discoverThreadCounter.get();
            final long startOfWait = System.currentTimeMillis();
            while (this.discoverThreadCounter.get() == lastDisoverValue) {
                java.lang.Thread.sleep(100);

                // Safety check
                if (System.currentTimeMillis() > startOfWait + 1000) { throw new IllegalStateException("We are waiting way too long."); }
            }
        }

        for (DiscoverOption option : options) {

            // If we have a callback option, add to request list.
            if (option instanceof OptionCallback) {
                this.allRequests.add(new CallbackRequest(this, plugin, (OptionCallback) option, options));
            }
        }

        // Eventually resolve the request.
        return resolve(plugin, options);

    }

    final AtomicInteger getPingTime(final DiscoveryManager mgr, final String address,
                                    final int port) {
        final AtomicInteger pingTime = new AtomicInteger(Integer.MAX_VALUE);

        // Get ping time
        AccessController.doPrivileged(new PrivilegedAction<Object>() {
            public Object run() {
                try {
                    final long start = System.nanoTime();

                    // Perform the ping
                    mgr.ping(new Random().nextInt());

                    // Obtain the time
                    final long stop = System.nanoTime();
                    final long time = (stop - start) / 1000;

                    // And set it
                    RemoteDiscoveryImpl.this.logger.finer("Ping time to manager was " + time + "µs");
                    pingTime.set((int) time);
                } catch (Exception e) {
                    RemoteDiscoveryImpl.this.logger.info("Error getting ping time of remote manager " + address + ":" + port);
                }
                return null;
            }
        });

        return pingTime;
    }

    void syso(final String s) {
        synchronized (System.out) {
            System.out.println(s);
        }
    }
}
TOP

Related Classes of net.xeoh.plugins.remotediscovery.impl.v3.RemoteDiscoveryImpl

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.