Package org.jboss.mx.server.registry

Source Code of org.jboss.mx.server.registry.BasicMBeanRegistry

/*
* JBoss, the OpenSource J2EE webOS
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package org.jboss.mx.server.registry;

import javax.management.Descriptor;
import javax.management.DynamicMBean;
import javax.management.InstanceAlreadyExistsException;
import javax.management.InstanceNotFoundException;
import javax.management.MalformedObjectNameException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.MBeanRegistration;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MBeanServerDelegate;
import javax.management.MBeanServerNotification;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.RuntimeErrorException;
import javax.management.RuntimeOperationsException;
import javax.management.modelmbean.ModelMBeanInfo;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Vector;

import org.jboss.mx.loading.LoaderRepository;

import org.jboss.mx.logging.Logger;
import org.jboss.mx.logging.SystemLogger;

import org.jboss.mx.metadata.MBeanCapability;

import org.jboss.mx.modelmbean.ModelMBeanConstants;
import org.jboss.mx.modelmbean.XMBean;
import org.jboss.mx.modelmbean.XMBeanConstants;

import org.jboss.mx.server.MBeanInvoker;
import org.jboss.mx.server.RawDynamicInvoker;
import org.jboss.mx.server.ServerObjectInstance;

import org.jboss.mx.util.ObjectNamePatternHelper;
import org.jboss.mx.util.ObjectNamePatternHelper.PropertyPattern;

import EDU.oswego.cs.dl.util.concurrent.SynchronizedLong;

/**
* The registry for object name - object reference mapping in the
* MBean server.
* <p>
* The implementation of this class affects the invocation speed
* directly, please check any changes for performance.
*
* @todo JMI_DOMAIN isn't very protected
*
* @see org.jboss.mx.server.registry.MBeanRegistry
*
* @author  <a href="mailto:juha@jboss.org">Juha Lindfors</a>.
* @author  <a href="mailto:trevor@protocool.com">Trevor Squires</a>.
* @author  <a href="mailto:Adrian.Brock@HappeningTimes.com">Adrian Brock</a>.
* @author  <a href="mailto:jhaynie@vocalocity.net">Jeff Haynie</a>.
* @version $Revision: 1.33 $
*/
public class BasicMBeanRegistry
   implements MBeanRegistry
{
   // Attributes ----------------------------------------------------

   /**
    * A map of domain name to another map containing object name canonical
    * key properties to registry entries.
    * domain -> canonicalKeyProperties -> MBeanEntry
    */
   private Map domainMap = new HashMap();

   /**
    * The default domain for this registry
    */
   private String defaultDomain;

   /**
    * The MBeanServer for which we are the registry.
    */
   private MBeanServer server = null;

   /**
    * The loader repository for loading classes
    */
   private LoaderRepository loaderRepository  = LoaderRepository.getDefaultLoaderRepository();

   /**
    * Sequence number for the MBean server registration notifications.
    */
   protected final SynchronizedLong registrationNotificationSequence = new SynchronizedLong (1);

   /**
    * Sequence number for the MBean server unregistration notifications.
    */
   protected final SynchronizedLong unregistrationNotificationSequence = new SynchronizedLong (1);

   /**
    * Direct reference to the mandatory MBean server delegate MBean.
    */
   protected MBeanServerDelegate delegate = null;

   protected Vector fMbInfosToStore;
   
   // Static --------------------------------------------------------

   /**
    * The logger
    */
   protected static Logger log = SystemLogger.getLogger(BasicMBeanRegistry.class);

   // Constructors --------------------------------------------------

   /**
    * Constructs a new BasicMBeanRegistry.<p>
    *
    * @param the default domain of this registry.
    */
   public BasicMBeanRegistry(MBeanServer server, String defaultDomain)
   {
      // Store the context
      this.server = server;
      this.defaultDomain = defaultDomain;
   }

   // MBeanRegistry Implementation ----------------------------------

   public ObjectInstance registerMBean(Object object, ObjectName name,
                                       Map valueMap)
      throws InstanceAlreadyExistsException,
             MBeanRegistrationException,
             NotCompliantMBeanException
   {
      ObjectName regName         = name;
      boolean registrationDone   = true;
      String magicToken          = null;
      MBeanInvoker invoker       = null;
     
      if (object == null)
         throw new RuntimeOperationsException(
               new IllegalArgumentException("Attempting to register null object"));

      // FIXME: This is untidy, in the pathological case it is
      //        wrong because we modifying the entry parameter
      //        which could get reused by the caller
      // This is a fix for the SAR TCL problem in 4.0
      // Allow the classloader in the value map to be an ObjectName
      if (valueMap != null)
      {
         Object obj = valueMap.get(CLASSLOADER);
         if (obj != null && obj instanceof ObjectName)
         {
            MBeanEntry clEntry = null;
            try
            {
                clEntry = get((ObjectName) obj);
            }
            catch (InstanceNotFoundException e)
            {
               throw new RuntimeOperationsException(
                  new IllegalArgumentException(e.toString()));
            }
            valueMap.put(CLASSLOADER, clEntry.getResourceInstance());
         }
      }

      // Check the objects compliance
      MBeanCapability mbcap = MBeanCapability.of(object.getClass());

      try
      {

         if (valueMap != null)
            magicToken = (String) valueMap.get(JMI_DOMAIN);

         // TODO: allow custom factory for diff invoker types
     
         if (mbcap.getMBeanType() == MBeanCapability.STANDARD_MBEAN)
         {
            invoker = new XMBean(object, XMBeanConstants.STANDARD_MBEAN);
         }
         else if (object instanceof MBeanInvoker)
         {
            invoker = (MBeanInvoker)object;
         }
         else if (object instanceof DynamicMBean)
         {
            invoker = new RawDynamicInvoker((DynamicMBean)object);
         }


         regName = invokePreRegister(invoker, regName, magicToken);

         try
         {
            // Register the mbean

            // Test for Null MBeanInfo
            if (invoker.getMBeanInfo() == null)
               throw new NotCompliantMBeanException("Null MBeanInfo for " + name);
           
            // FIXME redundant getResource()
            MBeanEntry entry = new MBeanEntry(regName, invoker, invoker.getResource(), valueMap);

            add(entry);

            try
            {
               // Add the classloader to the repository
               if (object instanceof ClassLoader)
                  registerClassLoader((ClassLoader)object);

               try
               {
                  if (delegate != null)
                  {
                     sendRegistrationNotification (regName);
                  }
 
                  else if (name.getCanonicalName ().equals (MBEAN_SERVER_DELEGATE))
                  {
                     delegate = (MBeanServerDelegate) object;
                  }
                 
                  ServerObjectInstance serverObjInst = new ServerObjectInstance (regName,
                             entry.getResourceClassName (),
                                                   delegate.getMBeanServerId ());
                  persistIfRequired(invoker.getMBeanInfo(), regName);
                  return serverObjInst;

               }
               catch (Throwable t)
               {
                  // Problem, remove a classloader from the repository
                  if (object instanceof ClassLoader)
                     loaderRepository.removeClassLoader((ClassLoader)object);
                    
                  throw t;
               }
            }
            catch (Throwable t)
            {
               // Problem, remove the mbean from the registry
               remove(regName);
              
               throw t;
            }
         }
         // Throw for null MBeanInfo
         catch (NotCompliantMBeanException e)
         {
            throw e;
         }
         // Thrown by the registry
         catch (InstanceAlreadyExistsException e)
         {
            throw e;
         }
         catch (Throwable t)
         {
            // Something is broken
            log.error("Unexpected Exception:", t);
           
            throw t;
         }
      }
      catch (NotCompliantMBeanException e)
      {
         registrationDone = false;
         throw e;
      }
      catch (InstanceAlreadyExistsException e)
      {
         // It was already registered
         registrationDone = false;
         throw e;
      }
      catch (MBeanRegistrationException e)
      {
         // The MBean cancelled the registration
         registrationDone = false;

         log.warn(e.toString());
        
         throw e;
      }
      catch (RuntimeOperationsException e)
      {
         // There was a problem with one the arguments
         registrationDone = false;
         throw e;
      }
      catch (Throwable t)
      {
         // Some other error
         registrationDone = false;
         return null;
      }
      finally
      {
         // Tell the MBean the result of the registration
         //if (registrationInterface != null)
         //   registrationInterface.postRegister(new Boolean(registrationDone));
         if (invoker != null)
            invoker.postRegister(new Boolean(registrationDone));
      }
    }

    /**
     * send a MBeanServerNotification.REGISTRATION_NOTIFICATION notification
     * to regName
     *
     * @param regName
     */
    protected void sendRegistrationNotification (ObjectName regName)
    {
        long sequence = registrationNotificationSequence.increment ();
        delegate.sendNotification (
                new MBeanServerNotification (
                        MBeanServerNotification.REGISTRATION_NOTIFICATION,
                        delegate, sequence, regName));
    }

    /**
     * subclasses can override to provide their own pre-registration pre- and post- logic for
     * <tt>preRegister</tt> and must call preRegister on the MBeanRegistration instance
     *
     * @param registrationInterface
     * @param regName
     * @return object name
     * @throws Exception
     * @author <a href="mailto:jhaynie@vocalocity.net">Jeff Haynie</a>
     */
    protected ObjectName handlePreRegistration (MBeanRegistration registrationInterface, ObjectName regName)
            throws Exception
    {
        ObjectName mbean = registrationInterface.preRegister (server, regName);
        if (regName == null)
        {
            return mbean;
        }
        else
        {
            return regName;
        }
    }

    protected void registerClassLoader(ClassLoader cl)
    {
       loaderRepository.addClassLoader(cl);
    }
   
    /**
     * subclasses can override to provide any custom preDeregister logic
     * and must call preDregister on the MBeanRegistration instance
     *
     * @param registrationInterface
     * @throws Exception
     * @author <a href="mailto:jhaynie@vocalocity.net">Jeff Haynie</a>
     */
    protected void handlePreDeregister (MBeanRegistration registrationInterface)
            throws Exception
    {
        registrationInterface.preDeregister ();
    }

   public void unregisterMBean(ObjectName name)
      throws InstanceNotFoundException, MBeanRegistrationException
   {
      name = qualifyName(name);
      if (name.getDomain().equals(JMI_DOMAIN))
         throw new RuntimeOperationsException(new IllegalArgumentException(
            "Not allowed to unregister: " + name.toString()));

      MBeanEntry entry = get(name);
      Object resource = entry.getResourceInstance();

      try
      {
          // allow subclasses to perform their own pre- and post- pre-deregister logic
          handlePreDeregister (entry.getInvoker());
           
      }
      catch (Exception e)
      {
         // don't double wrap MBeanRegistrationException
         if (e instanceof MBeanRegistrationException)
            throw (MBeanRegistrationException)e;
           
         throw new MBeanRegistrationException(e, "preDeregister");
      }

      // Remove any classloader
      if (resource instanceof ClassLoader)
         loaderRepository.removeClassLoader((ClassLoader)resource);

      // It is no longer registered
      remove(name);

      sendUnRegistrationNotification (name);

      entry.getInvoker().postDeregister();
   }

  /**
   * send MBeanServerNotification.UNREGISTRATION_NOTIFICATION notification to
   * name
   *
   * @param name
   */
  protected void sendUnRegistrationNotification (ObjectName name)
  {
      long sequence = unregistrationNotificationSequence.increment ();

      delegate.sendNotification (
              new MBeanServerNotification (
                      MBeanServerNotification.UNREGISTRATION_NOTIFICATION,
                      delegate,
                      sequence,
                      name
              )
      );
  }

   public MBeanEntry get(ObjectName name)
      throws InstanceNotFoundException
   {
      if (name == null)
         throw new InstanceNotFoundException("null object name");
        
      // Determine the domain and retrieve its entries
      String domain = name.getDomain();
     
      if (domain.length() == 0)
         domain = defaultDomain;
     
      String props = name.getCanonicalKeyPropertyListString();
      Map mbeanMap = getMBeanMap(domain, false);

      // Retrieve the mbean entry
      Object o = null;
      if (null == mbeanMap || null == (o = mbeanMap.get(props)))
         throw new InstanceNotFoundException(name + " is not registered.");

      // We are done
      return (MBeanEntry) o;
   }

   public String getDefaultDomain()
   {
      return defaultDomain;
   }

   public String[] getDomains()
   {
      ArrayList domains = new ArrayList(domainMap.size());
      synchronized (domainMap)
      {
         for (Iterator iterator = domainMap.entrySet().iterator(); iterator.hasNext();)
         {
            Map.Entry entry = (Map.Entry) iterator.next();
            String domainName = (String) entry.getKey();
            Map mbeans = (Map) entry.getValue();
            if (mbeans.isEmpty() == false)
               domains.add(domainName);
         }
      }
      return (String[]) domains.toArray(new String[domains.size()]);
   }

   public ObjectInstance getObjectInstance(ObjectName name)
      throws InstanceNotFoundException
   {
      if (!contains(name))
         throw new InstanceNotFoundException(name + " not registered.");

      return new ServerObjectInstance(qualifyName(name),
         get(name).getResourceClassName(), delegate.getMBeanServerId());
   }

   public Object getValue(ObjectName name, String key)
      throws InstanceNotFoundException
   {
      return get(name).getValue(key);
   }

   public boolean contains(ObjectName name)
   {
      // null safety check
      if (name == null)
         return false;

      // Determine the domain and retrieve its entries
      String domain = name.getDomain();
     
      if (domain.length() == 0)
         domain = defaultDomain;
     
      String props = name.getCanonicalKeyPropertyListString();
      Map mbeanMap = getMBeanMap(domain, false);

      // Return the result
      return (null != mbeanMap && mbeanMap.containsKey(props));
   }

   public  int getSize()
   {
      int retval = 0;
      synchronized (domainMap)
      {
   for (Iterator iterator = domainMap.values().iterator(); iterator.hasNext();)
   {
      retval += ((Map)iterator.next()).size();
   }
   return retval;
      }
   }

   public List findEntries(ObjectName pattern)
   {
      ArrayList retval = new ArrayList();

      // There are a couple of shortcuts we can employ to make this a
      // bit faster - they're commented.

      // First, if pattern == null or pattern.getCanonicalName() == "*:*" we want the
      // set of all MBeans.
      if (pattern == null || pattern.getCanonicalName().equals("*:*"))
      {
          synchronized (domainMap)
          {
             for (Iterator domainIter = domainMap.values().iterator(); domainIter.hasNext();)
                retval.addAll(((Map)domainIter.next()).values());
          }
      }
      // Next, if !pattern.isPattern() then we are doing a simple get (maybe defaultDomain).
      else if (!pattern.isPattern())
      {
         // simple get
         try
         {
            retval.add(get(pattern));
         }
         catch (InstanceNotFoundException e)
         {
            // we don't care
         }
      }
      // Now we have to do a brute force, oh well.
      else
      {
         String patternDomain = pattern.getDomain();
         if (patternDomain.length() == 0)
            patternDomain = defaultDomain;
         PropertyPattern propertyPattern = new PropertyPattern(pattern);

         // Here we go, step through every domain and see if our pattern matches before optionally checking
         // each ObjectName's properties for a match.
         synchronized (domainMap)
         {
            for (Iterator domainIter = domainMap.entrySet().iterator(); domainIter.hasNext();)
            {
               Map.Entry mapEntry = (Map.Entry) domainIter.next();
               if (ObjectNamePatternHelper.patternMatch((String) mapEntry.getKey(), patternDomain))
               {
                  for (Iterator mbeanIter = ((Map)mapEntry.getValue()).values().iterator(); mbeanIter.hasNext();)
                  {
                     MBeanEntry entry = (MBeanEntry) mbeanIter.next();
                     if (propertyPattern.patternMatch(entry.getObjectName()))
                        retval.add(entry);
                  }
               }
            }
         }
      }

      return retval;
   }

   // Protected -----------------------------------------------------

   protected ObjectName invokePreRegister(MBeanInvoker invoker, ObjectName regName, String magicToken)
      throws MBeanRegistrationException
   {

      // if we were given a non-null object name for registration, qualify it
      // and expand default domain
      if (regName != null)
         regName = qualifyName(regName);
  
      // store the name returned by preRegister() here
      ObjectName mbeanName = null;
     
      try
      {
         // invoke preregister on the invoker, it will delegate to the resource
         // if needed
         mbeanName = invoker.preRegister(server, regName);
      }
     
      // catch all exceptions cause by preRegister, these will abort registration
      catch (Exception e)
      {
         if (e instanceof MBeanRegistrationException)
         {
            throw (MBeanRegistrationException)e;
         }
        
         throw new MBeanRegistrationException(e,
               "preRegister() failed: " +
               "[ObjectName='" + regName +
               "', Class=" + invoker.getResource().getClass().getName() +
               " (" + invoker.getResource() + ")]"
         );
      }
      catch (Throwable t)
      {        
         log.warn("preRegister() failed for " + regName + ": ", t);
        
         if (t instanceof Error)
            throw new RuntimeErrorException((Error)t);
         else
            throw new RuntimeException(t.toString());
      }
          

      // if registered with null name, use the default name returned by
      // the preregister implementation
      if (regName == null)
         regName = mbeanName;
  
      return validateAndQualifyName(regName, magicToken);     
   }

   /**
    * Adds an MBean entry<p>
    *
    * WARNING: The object name should be fully qualified.
    *
    * @param entry the MBean entry to add
    * @exception InstanceAlreadyExistsException when the MBean's object name
    *            is already registered
    */
   protected synchronized void add(MBeanEntry entry)
      throws InstanceAlreadyExistsException
   {
      // Determine the MBean's name and properties
      ObjectName name = entry.getObjectName();
      String domain = name.getDomain();
      String props = name.getCanonicalKeyPropertyListString();

      // Create a properties -> entry map if we don't have one
      Map mbeanMap = getMBeanMap(domain, true);

      // Make sure we aren't already registered
      if (mbeanMap.get(props) != null)
         throw new InstanceAlreadyExistsException(name + " already registered.");

      // Ok, we are registered
      mbeanMap.put(props, entry);
   }

   /**
    * Removes an MBean entry
    *
    * WARNING: The object name should be fully qualified.
    *
    * @param name the object name of the entry to remove
    * @exception InstanceNotFoundException when the object name is not
    *            registered
    */
   protected synchronized void remove(ObjectName name)
      throws InstanceNotFoundException
   {
      // Determine the MBean's name and properties
      String domain = name.getDomain();
      String props = name.getCanonicalKeyPropertyListString();
      Map mbeanMap = getMBeanMap(domain, false);

      // Remove the entry, raise an exception when it didn't exist
      if (null == mbeanMap || null == mbeanMap.remove(props))
         throw new InstanceNotFoundException(name + " not registered.");
   }

   /**
    * Validates and qualifies an MBean<p>
    *
    * Validates the name is not a pattern.<p>
    *
    * Adds the default domain if no domain is specified.<p>
    *
    * Checks the name is not in the reserved domain JMImplementation when
    * the magicToken is not {@link org.jboss.mx.server.ServerConstants#JMI_DOMAIN JMI_DOMAIN}
    *
    * @param name the name to validate
    * @param magicToken used to get access to the reserved domain
    * @return the original name or the name prepended with the default domain
    *         if no domain is specified.
    * @exception RuntimeOperationException containing an
    *            IllegalArgumentException for a problem with the name
    */
   protected ObjectName validateAndQualifyName(ObjectName name,
                                               String magicToken)
   {
      // Check for qualification
      ObjectName result = qualifyName(name);
     
      // Make sure the name is not a pattern
      if (result.isPattern())
         throw new RuntimeOperationsException(
               new IllegalArgumentException("Object name is a pattern:" + name));

      // Check for reserved domain
      if (magicToken != JMI_DOMAIN &&
          result.getDomain().equals(JMI_DOMAIN))
         throw new RuntimeOperationsException(new IllegalArgumentException(
                     "Domain " + JMI_DOMAIN + " is reserved"));

      // I can't think of anymore tests, we're done
      return result;
   }

   /**
    * Qualify an object name with the default domain<p>
    *
    * Adds the default domain if no domain is specified.
    *
    * @param name the name to qualify
    * @return the original name or the name prepended with the default domain
    *         if no domain is specified.
    * @exception RuntimeOperationException containing an
    *            IllegalArgumentException when there is a problem
    */
   protected ObjectName qualifyName(ObjectName name)
   {
      if (name == null)
         throw new RuntimeOperationsException(
               new IllegalArgumentException("Null object name"));
      try
      {
         if (name.getDomain().length() == 0)
            return new ObjectName(defaultDomain + ":" +
                                  name.getCanonicalKeyPropertyListString());
         else
            return name;
      }
      catch (MalformedObjectNameException e)
      {
         throw new RuntimeOperationsException(
               new IllegalArgumentException(e.toString()));
      }
   }
  
   /**
    * Adds the given MBean Info object to the persistence queue if it explicity denotes
    * (via metadata) that it should be stored.
    * @todo -- add notification of registration of MBeanInfoDb.
    *   It is possible that some MBeans whose MBean Info should be stored are
    *   registered before the MBean Info Storage delegate is available.  These
    *   MBeans are remembered by the registry and should be added to the storage delegate
    *   as soon as it is available.  In the current mechanism, they are added only if another
    *   MBean requesting MBean info persistence is registered after the delegate is registered.
    *   Someone more familiar with the server could make this more robust by adding
    *   a notification mechanism such that the queue is flushed as soon as the
    *   delegate is available.  - Matt Munz
    */
   protected void persistIfRequired(MBeanInfo info, ObjectName name)
     throws
       MalformedObjectNameException,
       InstanceNotFoundException,
       MBeanException,
       ReflectionException
   {
     if(!(info instanceof ModelMBeanInfo))
     {
         return;
     }
     ModelMBeanInfo mmbInfo = (ModelMBeanInfo) info;
     Descriptor[] descriptors;
     try { descriptors = mmbInfo.getDescriptors(ModelMBeanConstants.MBEAN_DESCRIPTOR); }
     catch(MBeanException cause)
     {
       log.error("Error trying to get descriptors.", cause);
       return;
     }
     log.debug("descriptors: " + descriptors);
     if(descriptors == null) { return; }
     String persistInfo = null;
     for(int i = 0; ((i < descriptors.length) && (persistInfo == null)); i++)
     {
       persistInfo = (String) descriptors[i].getFieldValue(ModelMBeanConstants.PERSIST_INFO);
     }
     if(persistInfo == null) { return; } // use default -- no persistence
     log.debug("persistInfo: " + persistInfo);
     Boolean shouldPersist = new Boolean(persistInfo);
     if(!shouldPersist.booleanValue())
     {
        return;
     }
     mbInfosToStore().add(name);
     // see if MBeanDb is available
     ObjectName mbeanInfoService = new ObjectName("user:service=MBeanInfoDB");
     if(server.isRegistered(mbeanInfoService))
     {
       // flush queue to the MBeanDb
       log.debug("flushing queue");
       server.invoke(
         mbeanInfoService,
         "add",
         new Object[] { mbInfosToStore().clone() },
         new String[] { mbInfosToStore().getClass().getName() });      
       log.debug("clearing queue");
       mbInfosToStore().clear();
     }
     else
     {
       log.debug("service is not registered.  items remain in queue");
     }
   }
  
   /**
    * ObjectName objects bound to MBean Info objects that are waiting to be stored in the
    * persistence store.
    */
   protected Vector mbInfosToStore()
   {
      if(fMbInfosToStore == null)
      {
         fMbInfosToStore = new Vector(10);
      }
      return fMbInfosToStore;
   }  

   /**
    * The <code>getMBeanMap</code> method provides synchronized access
    * to the mbean map for a domain.  This is actually a solution to a
    * bug that resulted in wiping out the jboss domain mbeanMap for no
    * apparent reason.
    *
    * @param domain a <code>String</code> value
    * @param createIfMissing a <code>boolean</code> value
    * @return a <code>Map</code> value
    */
   private Map getMBeanMap(String domain, boolean createIfMissing)
   {
      synchronized (domainMap)
      {
   Map mbeanMap = (Map)domainMap.get(domain);
   if (mbeanMap == null && createIfMissing)
   {
      mbeanMap = new HashMap();
      domainMap.put(domain, mbeanMap);
   } // end of if ()
   return mbeanMap;
      }
   }  
}
TOP

Related Classes of org.jboss.mx.server.registry.BasicMBeanRegistry

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.