Package org.apache.qpid.server.qmf2

Source Code of org.apache.qpid.server.qmf2.QmfManagementAgent

/*
*
* 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.qpid.server.qmf2;

// Misc Imports
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

// Simple Logging Facade 4 Java
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// QMF2 Imports
import org.apache.qpid.qmf2.agent.Agent;
import org.apache.qpid.qmf2.agent.MethodCallParams;
import org.apache.qpid.qmf2.agent.MethodCallWorkItem;
import org.apache.qpid.qmf2.agent.QmfAgentData;
import org.apache.qpid.qmf2.common.ObjectId;
import org.apache.qpid.qmf2.common.QmfData;
import org.apache.qpid.qmf2.common.QmfEvent;
import org.apache.qpid.qmf2.common.QmfEventListener;
import org.apache.qpid.qmf2.common.QmfException;
import org.apache.qpid.qmf2.common.QmfType;
import org.apache.qpid.qmf2.common.WorkItem;
import org.apache.qpid.qmf2.util.ConnectionHelper;
import static org.apache.qpid.qmf2.common.WorkItem.WorkItemType.*;

// Java Broker model Imports
import org.apache.qpid.server.model.Binding;
import org.apache.qpid.server.model.Broker;
import org.apache.qpid.server.model.ConfigurationChangeListener;
import org.apache.qpid.server.model.ConfiguredObject;
import org.apache.qpid.server.model.Connection;
import org.apache.qpid.server.model.Consumer;
import org.apache.qpid.server.model.Exchange;
import org.apache.qpid.server.model.LifetimePolicy;
import org.apache.qpid.server.model.Port;
import org.apache.qpid.server.model.Publisher;
import org.apache.qpid.server.model.Queue;
import org.apache.qpid.server.model.Session;
import org.apache.qpid.server.model.State;
import org.apache.qpid.server.model.VirtualHost;

/**
* This class implements a QMF2 Agent providing access to the Java broker Management Objects via QMF2 thus
* allowing the Java Broker to be managed in the same way to the C++ Broker.
* <p>
* The intention is for the QmfManagementAgent to conform to the same Management Schema as the C++ Broker
* (e.g. as specified in management-schema.xml) in order to provide maximum cohesion between the
* two Broker implementations, however that's not entirely possible given differences between the underlying
* Management Models.
* <p>
* This Plugin attempts to map properties from the Java org.apache.qpid.server.model.* classes to equivalent
* properties and statistics in the C++ broker's Management Schema rather than expose them "natively", this is
* in order to try and maximise alignment between the two implementations and to try to allow the Java Broker
* to be managed by the Command Line tools used with the C++ Broker such as qpid-config etc. it's also to
* enable the Java Broker to be accessed via the QMF2 REST API and GUI.
*
* @author Fraser Adams
*/
public class QmfManagementAgent implements ConfigurationChangeListener, QmfEventListener
{
    private static final Logger _log = LoggerFactory.getLogger(QmfManagementAgent.class);

    // Set heartbeat interval to 10 seconds. TODO Should probably be config driven, but I *think* that this is
    // different than "heartbeat.delay" and "heartbeat.timeoutFactor" currently present in the config?
    private static final int HEARTBEAT_INTERVAL = 10;
    private Agent _agent = null;

    // The first Connection Object relates to the QmfManagementAgent, we use this flag to avoid mapping that Connection
    // to a QMF Object thus hiding it from Consoles. This is done to provide consistency with the C++ Broker which
    // also "hides" its own private AMQP Connections, Queues & Bindings.
    private boolean agentConnection = true;

    private final Broker<?> _broker;          // Passed in by Plugin bootstrapping.
    private final String _defaultVirtualHost; // Pulled from the broker attributes.

    /**
     * A Map of QmfAgentData keyed by ConfiguredObject. This is mainly used for Management Object "lifecycle management".
     * In an ideal world the Agent class could retain all information, but I want to track ConfiguredObject state and
     * use that to create and delete QmfAgentData data, which means I need to be able to *find* the QmfAgentData and
     * the *official* Agent API doesn't have a public method to query Objects (though the evaluateQuery() method used
     * by Query Subscriptions could be used, but it's not really a "public API method" so probably best not to use it)
     * Arguably this is what the AgentExternal subclass of Agent is for whereby queries are handled in the Agent
     * implementation by handling "(wi.getType() == QUERY)" but the AgentExternal API forces some constructs that are
     * actually likely to be less efficient, as an example sending a separate queryResponse() for each object forces a
     * look up of a List of QmfAgentData objects keyed by the consoleHandle for each call. There is also the need to
     * separately iterate through the List of QmfAgentData objects thus created to create the mapEncoded list needed
     * for sending via the QMF2 protocol.
     * <p>
     * So rather than go through all that faff we simply retain an additional Map as below which allows navigation
     * between the ConfiguredObject and QmfAgentData. The subclasses of QmfAgentData will contain references to
     * allow navigation back to the concrete subclasses of ConfiguredObject if necessary.
     * The capacity of 100 is pretty arbitrary but the default of 16 seems too low for a ManagementAgent.
     */
    private Map<ConfiguredObject, QmfAgentData> _objects = new ConcurrentHashMap<ConfiguredObject, QmfAgentData>(100);

    /**
     * Constructor. Creates the AMQP Connection to the Broker and starts the QMF2 Agent.
     * @param url the Connection URL to be used to construct the AMQP Connection.
     * @param broker the root Broker Management Object from which the other Management Objects may be obtained.
     * to work without explicitly setting a Virtual Host, which I think is necessary because the C++ Broker and
     * the python command line tools aren't currently Virtual Host aware (are they?). The intention is to mark
     * queues and exchanges with [vhost:<vhost-name>/]<object-name> in other words if we want to add things to
     * the non-default Virtual Host prefix their names with [vhost:<vhost-name>/]. This approach *ought* to allow
     * non-Virtual Host aware command line tools the ability to add queues/exchanges to a particular vhost.
     */
    public QmfManagementAgent(final String url, final Broker broker)
    {
        _broker = broker;
        _defaultVirtualHost = (String)broker.getAttribute("defaultVirtualHost");

        try
        {
            // Create the actual JMS Connection. ConnectionHelper allows us to work with a variety of URL
            // formats so we can abstract away from the somewhat complex Java AMQP URL format.
            javax.jms.Connection connection = ConnectionHelper.createConnection(url);
            if (connection == null)
            {
                _log.info("QmfManagementAgent Constructor failed due to null AMQP Connection");
            }
            else
            {
                _agent = new Agent(this, HEARTBEAT_INTERVAL);
                // Vendor and Product are deliberately set to be the same as for the C++ broker.
                _agent.setVendor("apache.org");
                _agent.setProduct("qpidd");
                _agent.setConnection(connection);

                // Register the schema for the Management Objects. These don't have to be completely populated
                // the minimum is to register package name and class name for the QmfAgentData.
                _agent.registerObjectClass(org.apache.qpid.server.qmf2.agentdata.Broker.getSchema());
                _agent.registerObjectClass(org.apache.qpid.server.qmf2.agentdata.Connection.getSchema());
                _agent.registerEventClass(org.apache.qpid.server.qmf2.agentdata.Connection.getClientConnectSchema());
                _agent.registerEventClass(org.apache.qpid.server.qmf2.agentdata.Connection.getClientDisconnectSchema());

                _agent.registerObjectClass(org.apache.qpid.server.qmf2.agentdata.Exchange.getSchema());
                _agent.registerEventClass(org.apache.qpid.server.qmf2.agentdata.Exchange.getExchangeDeclareSchema());
                _agent.registerEventClass(org.apache.qpid.server.qmf2.agentdata.Exchange.getExchangeDeleteSchema());

                _agent.registerObjectClass(org.apache.qpid.server.qmf2.agentdata.Queue.getSchema());
                _agent.registerEventClass(org.apache.qpid.server.qmf2.agentdata.Queue.getQueueDeclareSchema());
                _agent.registerEventClass(org.apache.qpid.server.qmf2.agentdata.Queue.getQueueDeleteSchema());

                _agent.registerObjectClass(org.apache.qpid.server.qmf2.agentdata.Binding.getSchema());
                _agent.registerEventClass(org.apache.qpid.server.qmf2.agentdata.Binding.getBindSchema());
                _agent.registerEventClass(org.apache.qpid.server.qmf2.agentdata.Binding.getUnbindSchema());

                _agent.registerObjectClass(org.apache.qpid.server.qmf2.agentdata.Subscription.getSchema());
                _agent.registerEventClass(org.apache.qpid.server.qmf2.agentdata.Subscription.getSubscribeSchema());
                _agent.registerEventClass(org.apache.qpid.server.qmf2.agentdata.Subscription.getUnsubscribeSchema());

                _agent.registerObjectClass(org.apache.qpid.server.qmf2.agentdata.Session.getSchema());

                // Initialise QmfAgentData Objects and track changes to the broker Management Objects.
                registerConfigurationChangeListeners();
            }
        }
        catch (QmfException qmfe)
        {
            _log.info("QmfException {} caught in QmfManagementAgent Constructor", qmfe.getMessage());
            _agent = null; // Causes isConnected() to be false and thus prevents the "QMF2 Management Ready" message.
        }
        catch (Exception e)
        {
            _log.info("Exception {} caught in QmfManagementAgent Constructor", e.getMessage());
            _agent = null; // Causes isConnected() to be false and thus prevents the "QMF2 Management Ready" message.
        }
    }

    /**
     * Close the QmfManagementAgent clearing the QMF2 Agent and freeing its resources.
     */
    public void close()
    {
        if (isConnected())
        {
            _agent.destroy();
        }
    }

    /**
     * Returns whether the Agent is connected and running.
     * @return true if the Agent is connected and running otherwise return false.
     */
    public boolean isConnected()
    {
        return _agent != null;
    }

    /**
     * This method initialises the initial set of QmfAgentData Objects and tracks changes to the Broker Management
     * Objects via the childAdded() method call.
     */
    private void registerConfigurationChangeListeners()
    {
        childAdded(null, _broker);

        for (VirtualHost<?> vhost : _broker.getVirtualHosts())
        {
            // We don't add QmfAgentData VirtualHost objects. Possibly TODO, but it's a bit awkward at the moment
            // because the C++ Broker doesn't *seem* to do much with them and the command line tools such
            // as qpid-config don't appear to be VirtualHost aware. A way to stay compatible is to mark queues,
            // exchanges etc with [vhost:<vhost-name>/]<object-name> (see Constructor comments).
            vhost.addChangeListener(this);

            for (Connection<?> connection : vhost.getConnections())
            {
                childAdded(vhost, connection);
                for (Session<?> session : connection.getSessions())
                {
                    childAdded(connection, session);

                    if (session.getConsumers() != null)
                    {
                        for (Consumer subscription : session.getConsumers())
                        {
                            childAdded(session, subscription);
                        }
                    }
                }
            }

            // The code blocks for adding Bindings (and adding Queues) contain checks to see if what is being added
            // relates to Queues or Bindings for the QmfManagementAgent. If they are QmfManagementAgent related
            // we avoid registering the Object as a QMF Object, in other words we "hide" QmfManagementAgent QMF Objects.
            // This is done to be consistent with the C++ broker which also "hides" its own Connection, Queue & Binding.
            for (Exchange<?> exchange : vhost.getExchanges())
            {
                childAdded(vhost, exchange);

                for (Binding binding : exchange.getBindings())
                {
                    String key = binding.getName();
                    if (key.equals("broker") || key.equals("console.request.agent_locate") ||
                        key.startsWith("apache.org:qpidd:") || key.startsWith("TempQueue"))
                    { // Don't add QMF related Bindings in registerConfigurationChangeListeners as those will relate
                    } // to the Agent and we want to "hide" those.
                    else
                    {
                        childAdded(exchange, binding);
                    }
                }
            }

            for (Queue<?> queue : vhost.getQueues())
            {
                boolean agentQueue = false;
                for (Binding binding : queue.getBindings())
                {
                    String key = binding.getName();
                    if (key.equals("broker") || key.equals("console.request.agent_locate") ||
                        key.startsWith("apache.org:qpidd:"))
                    {
                        agentQueue = true;
                        break;
                    }
                }

                // Don't add QMF related bindings or Queues in registerConfigurationChangeListeners as those will
                // relate to the Agent itself and we want to "hide" those to be consistent with the C++ Broker.
                if (!agentQueue)
                {
                    childAdded(vhost, queue);

                    for (Binding binding : queue.getBindings())
                    {
                        childAdded(queue, binding);
                    }

                    for (Consumer subscription : queue.getChildren(Consumer.class))
                    {
                        childAdded(queue, subscription);
                    }
                }
            }
        }
    }


    // ************************* ConfigurationChangeListener implementation methods *************************

    /**
     * ConfigurationChangeListener method called when the state is changed (ignored here).
     * @param object the object being modified.
     * @param oldState the state of the object prior to this method call.
     * @param newState the desired state of the object.
     */
    @Override
    public void stateChanged(final ConfiguredObject object, final State oldState, final State newState)
    {
        // no-op
    }

    /**
     * ConfigurationChangeListener method called when an attribute is set (ignored here).
     * @param object the object being modified.
     * @param attributeName the name of the object attribute that we want to change.
     * @param oldAttributeValue the value of the attribute prior to this method call.
     * @param newAttributeValue the desired value of the attribute.
     */
    @Override
    public void attributeSet(ConfiguredObject object, String attributeName,
                             Object oldAttributeValue, Object newAttributeValue)
    {
        // no-op
    }

    /**
     * ConfigurationChangeListener method called when a child ConfiguredObject is added.
     * <p>
     * This method checks the type of the child ConfiguredObject that has been added and creates the equivalent
     * QMF2 Management Object if one doesn't already exist. In most cases it's a one-to-one mapping, but for
     * Binding for example the Binding child is added to both Queue and Exchange so we only create the Binding
     * QMF2 Management Object once and add the queueRef and exchangeRef reference properties referencing the Queue
     * and Exchange parent Objects respectively, Similarly for Consumer (AKA Subscription).
     * <p>
     * This method is also responsible for raising the appropriate QMF2 Events when Management Objects are created.
     * @param object the parent object that the child is being added to.
     * @param child the child object being added.
     */
    @Override
    public void childAdded(final ConfiguredObject object, final ConfiguredObject child)
    {
//System.out.println("childAdded: " + child.getClass().getSimpleName() + "." + child.getName());

        QmfAgentData data = null;

        if (child instanceof Broker)
        {
            data = new org.apache.qpid.server.qmf2.agentdata.Broker((Broker)child);
        }
        else if (child instanceof Connection)
        {
            if (!agentConnection && !_objects.containsKey(child))
            {
                // If the parent object is the default vhost set it to null so that the Connection ignores it.
                VirtualHost vhost = (object.getName().equals(_defaultVirtualHost)) ? null : (VirtualHost)object;
                data = new org.apache.qpid.server.qmf2.agentdata.Connection(vhost, (Connection)child);
                _objects.put(child, data);

                // Raise a Client Connect Event.
                _agent.raiseEvent(((org.apache.qpid.server.qmf2.agentdata.Connection)data).createClientConnectEvent());
            }
            agentConnection = false; // Only ignore the first Connection, which is the one from the Agent.
        }
        else if (child instanceof Session)
        {
            if (!_objects.containsKey(child))
            {
                QmfAgentData ref = _objects.get(object); // Get the Connection QmfAgentData so we can get connectionRef.
                if (ref != null)
                {
                    data = new org.apache.qpid.server.qmf2.agentdata.Session((Session)child, ref.getObjectId());
                    _objects.put(child, data);
                }
            }
        }
        else if (child instanceof Exchange)
        {
            if (!_objects.containsKey(child))
            {
                // If the parent object is the default vhost set it to null so that the Connection ignores it.
                VirtualHost vhost = (object.getName().equals(_defaultVirtualHost)) ? null : (VirtualHost)object;
                data = new org.apache.qpid.server.qmf2.agentdata.Exchange(vhost, (Exchange)child);
                _objects.put(child, data);

                // Raise an Exchange Declare Event.
                _agent.raiseEvent(((org.apache.qpid.server.qmf2.agentdata.Exchange)data).createExchangeDeclareEvent());

            }
        }
        else if (child instanceof Queue)
        {
            if (!_objects.containsKey(child))
            {
                // If the parent object is the default vhost set it to null so that the Connection ignores it.
                VirtualHost vhost = (object.getName().equals(_defaultVirtualHost)) ? null : (VirtualHost)object;
                data = new org.apache.qpid.server.qmf2.agentdata.Queue(vhost, (Queue)child);
                _objects.put(child, data);

                // Raise a Queue Declare Event.
                _agent.raiseEvent(((org.apache.qpid.server.qmf2.agentdata.Queue)data).createQueueDeclareEvent());
            }
        }
        else if (child instanceof Binding)
        {
            // Bindings are a little more complex because in QMF bindings contain exchangeRef and queueRef properties
            // whereas with the Java Broker model Binding is a child of Queue and Exchange. To cope with this we
            // first try to create or retrieve the QMF Binding Object then add either the Queue or Exchange reference
            // depending on whether Queue or Exchange was the parent of this addChild() call.
            if (!_objects.containsKey(child))
            {
                data = new org.apache.qpid.server.qmf2.agentdata.Binding((Binding)child);
                _objects.put(child, data);

                String eName = ((Binding)child).getExchange().getName();
                if (!eName.equals("<<default>>")) // Don't send Event for Binding to default direct.
                {
                    // Raise a Bind Event.
                    _agent.raiseEvent(((org.apache.qpid.server.qmf2.agentdata.Binding)data).createBindEvent());
                }
            }

            org.apache.qpid.server.qmf2.agentdata.Binding binding =
                (org.apache.qpid.server.qmf2.agentdata.Binding)_objects.get(child);

            QmfAgentData ref = _objects.get(object);
            if (ref != null)
            {
                if (object instanceof Queue)
                {
                    binding.setQueueRef(ref.getObjectId());
                }
                else if (object instanceof Exchange)
                {
                    binding.setExchangeRef(ref.getObjectId());
                }
            }
        }
        else if (child instanceof Consumer) // AKA Subscription
        {
            // Subscriptions are a little more complex because in QMF Subscriptions contain sessionRef and queueRef
            // properties whereas with the Java Broker model Consumer is a child of Queue and Session. To cope with
            // this we first try to create or retrieve the QMF Subscription Object then add either the Queue or
            // Session reference depending on whether Queue or Session was the parent of this addChild() call.
            if (!_objects.containsKey(child))
            {
                data = new org.apache.qpid.server.qmf2.agentdata.Subscription((Consumer)child);
                _objects.put(child, data);
           }

            org.apache.qpid.server.qmf2.agentdata.Subscription subscription =
                (org.apache.qpid.server.qmf2.agentdata.Subscription)_objects.get(child);

            QmfAgentData ref = _objects.get(object);
            if (ref != null)
            {
                if (object instanceof Queue)
                {
                    subscription.setQueueRef(ref.getObjectId(), (Queue)object);
                    // Raise a Subscribe Event - N.B. Need to do it *after* we've set the queueRef.
                    _agent.raiseEvent(subscription.createSubscribeEvent());
                }
                else if (object instanceof Session)
                {
                    subscription.setSessionRef(ref.getObjectId());
                }
            }
        }

        try
        {
            // If we've created new QmfAgentData we register it with the Agent.
            if (data != null)
            {
                _agent.addObject(data);
            }
        }
        catch (QmfException qmfe)
        {
            _log.info("QmfException {} caught in QmfManagementAgent.addObject()", qmfe.getMessage());
        }

        child.addChangeListener(this);
    }


    /**
     * ConfigurationChangeListener method called when a child ConfiguredObject is removed.
     * <p>
     * This method checks the type of the child ConfiguredObject that has been removed and raises the appropriate
     * QMF2 Events, it then destroys the QMF2 Management Object and removes the mapping between child and the QMF Object.
     *
     * @param object the parent object that the child is being removed from.
     * @param child the child object being removed.
     */
    @Override
    public void childRemoved(final ConfiguredObject object, final ConfiguredObject child)
    {
//System.out.println("childRemoved: " + child.getClass().getSimpleName() + "." + child.getName());

        child.removeChangeListener(this);

        // Look up the associated QmfAgentData and mark it for deletion by the Agent.
        QmfAgentData data = _objects.get(child);

        if (data != null)
        {
            if (child instanceof Connection)
            {
                // Raise a Client Disconnect Event.
                _agent.raiseEvent(((org.apache.qpid.server.qmf2.agentdata.Connection)data).createClientDisconnectEvent());
            }
            else if (child instanceof Session)
            {
                // no-op, don't need to do anything specific when Session is removed.
            }
            else if (child instanceof Exchange)
            {
                // Raise an Exchange Delete Event.
                _agent.raiseEvent(((org.apache.qpid.server.qmf2.agentdata.Exchange)data).createExchangeDeleteEvent());
            }
            else if (child instanceof Queue)
            {
                // Raise a Queue Delete Event.
                _agent.raiseEvent(((org.apache.qpid.server.qmf2.agentdata.Queue)data).createQueueDeleteEvent());
            }
            else if (child instanceof Binding)
            {
                String eName = ((Binding)child).getExchange().getName();
                if (!eName.equals("<<default>>")) // Don't send Event for Unbinding from default direct.
                {
                    // Raise an Unbind Event.
                    _agent.raiseEvent(((org.apache.qpid.server.qmf2.agentdata.Binding)data).createUnbindEvent());
                }
            }
            else if (child instanceof Consumer)
            {
                // Raise an Unsubscribe Event.
                _agent.raiseEvent(((org.apache.qpid.server.qmf2.agentdata.Subscription)data).createUnsubscribeEvent());
            }

            data.destroy();
        }

        // Remove the mapping from the internal ConfiguredObject->QmfAgentData Map.
        _objects.remove(child);
    }

    // ******************************* QmfEventListener implementation method *******************************

    /**
     * Callback method triggered when the underlying QMF2 Agent has WorkItems available for processing.
     * The purpose of this method is mainly to handle the METHOD_CALL WorkItem and demultiplex & delegate
     * to the invokeMethod() call on the relevant concrete QmfAgentData Object.
     * @param wi the WorkItem that has been passed by the QMF2 Agent to be processed here (mainly METHOD_CALL).
     */
    @Override
    public void onEvent(final WorkItem wi)
    {
        if (wi.getType() == METHOD_CALL)
        {
            MethodCallWorkItem item = (MethodCallWorkItem)wi;
            MethodCallParams methodCallParams = item.getMethodCallParams();

            String methodName = methodCallParams.getName();
            ObjectId objectId = methodCallParams.getObjectId();

            // Look up QmfAgentData by ObjectId from the Agent's internal Object store.
            QmfAgentData object = _agent.getObject(objectId);
            if (object == null)
            {
                _agent.raiseException(item.getHandle(), "No object found with ID=" + objectId);
            }
            else
            {
                // If we've found a valid QmfAgentData check it's a Broker or Queue and if so call the generic
                // invokeMethod on these objects, if not send an Exception as we don't support methods on
                // other classes yet.
                if (object instanceof org.apache.qpid.server.qmf2.agentdata.Broker)
                {
                    org.apache.qpid.server.qmf2.agentdata.Broker broker =
                        (org.apache.qpid.server.qmf2.agentdata.Broker) object;
                    broker.invokeMethod(_agent, item.getHandle(), methodName, methodCallParams.getArgs());
                }
                else if (object instanceof org.apache.qpid.server.qmf2.agentdata.Queue)
                {
                    org.apache.qpid.server.qmf2.agentdata.Queue queue =
                        (org.apache.qpid.server.qmf2.agentdata.Queue) object;
                    queue.invokeMethod(_agent, item.getHandle(), methodName, methodCallParams.getArgs());
                }
                else
                {
                    _agent.raiseException(item.getHandle(), "Unknown Method " + methodName + " on " +
                                          object.getClass().getSimpleName());
                }
            }
        }
    }
}
TOP

Related Classes of org.apache.qpid.server.qmf2.QmfManagementAgent

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.