Package org.jasig.portal.tools.chanpub

Source Code of org.jasig.portal.tools.chanpub.ChannelPublisher

/* Copyright 2003 The JA-SIG Collaborative.  All rights reserved.
*  See license distributed with this file and
*  available online at http://www.uportal.org/license.html
*/

package org.jasig.portal.tools.chanpub;

import java.io.File;
import java.io.FileFilter;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.jasig.portal.ChannelCategory;
import org.jasig.portal.ChannelDefinition;
import org.jasig.portal.ChannelParameter;
import org.jasig.portal.ChannelRegistryStoreFactory;
import org.jasig.portal.ChannelType;
import org.jasig.portal.Constants;
import org.jasig.portal.EntityIdentifier;
import org.jasig.portal.IChannelRegistryStore;
import org.jasig.portal.RDBMServices;
import org.jasig.portal.groups.IEntity;
import org.jasig.portal.groups.IEntityGroup;
import org.jasig.portal.groups.IGroupConstants;
import org.jasig.portal.security.IAuthorizationPrincipal;
import org.jasig.portal.security.IPermission;
import org.jasig.portal.security.IPerson;
import org.jasig.portal.security.IUpdatingPermissionManager;
import org.jasig.portal.security.PersonFactory;
import org.jasig.portal.services.AuthorizationService;
import org.jasig.portal.services.GroupService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.portal.utils.ResourceLoader;
import org.jasig.portal.utils.XML;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

/**
* This is a Channel Publisher tool to install uPortal channels from outside of
* the portal or from within a channel archive.
* Currently configured to be executed via Jakarta Ant or via a
* channel-definition block within a CAR deployment descriptor.
*
* Sample of command line arguments:
* ant publish -Dchannel=all  (this will publish all channels that have a corresponding xml file)
* ant publish -Dchannel=webmail.xml  (this will publish the specified channels)
*
* @author Freddy Lopez, flopez@unicon.net
* @author Ken Weiner, kweiner@unicon.net
* @author Mark Boyd, mboyd@sct.com
* @version $Revision: 1.29 $
*/
public class ChannelPublisher implements ErrorHandler
{
    private static final Log log = LogFactory.getLog(ChannelPublisher.class);

    private static final String FRAMEWORK_OWNER = IPermission.PORTAL_FRAMEWORK;
    private static final String SUBSCRIBER_ACTIVITY =
        IPermission.CHANNEL_SUBSCRIBER_ACTIVITY;
    private static final String GRANT_PERMISSION_TYPE =
        IPermission.PERMISSION_TYPE_GRANT;

    private static final int LOAD_ALL_FILES = 0;
    private static final int LOAD_ONE_FILE = 1;

    private IPerson systemUser;
    private DocumentBuilder domParser;
    private IChannelRegistryStore crs;
    private Map chanTypesNamesToIds;
    private boolean mOnCommandLine = false;
    private boolean mOverrideExisting = false;

    private static final String chanDefsLocation = "/properties/chanpub";

    /**
     * @param args
     * @throws Exception We let Exceptions bubble up so that ant will know
     * that the publishing failed and can report the error message and stack
     * trace to the user.
     */
    public static void main(String[] args)throws Exception{
        try{
            RDBMServices.setGetDatasourceFromJndi(false); /*don't try jndi when not in web app */
            /*
            
             Channel Publisher Tool Workflow.
             1) read all or specified channel.xml file
            
             ant publish -Dchannel=all or -Dchannel=webmail.xml
            
             2) validate each against the channelDefinition.dtd file
            
             3) publish one channel at a time
            
             */
           
            // determine whether user wants to publish one or all of the channels in current directory
            int mode = LOAD_ALL_FILES;
           
            if (args[1] != null && args[1].length() > 0) {
                // MODE = 0 for all channels in directory
                // MODE = 1 for individual channel
                if (args[1].equals("all"))
                    mode = LOAD_ALL_FILES;
                else
                    mode = LOAD_ONE_FILE;
               
            }
           
            ChannelPublisher publisher = getCommandLineInstance();
           
            // determine what mode we are in
            if (mode == LOAD_ONE_FILE) {
                System.out.println(
                "You have chosen to publish one channel.....");
                System.out.print("Publishing channel " + args[1] + ".....");
                // lets publish one channel only
                publisher.publishChannel(args[1]);
                System.out.println("Done");
            } else {
                // lets publish all channels in directory
                System.out.println ("You have chosen to publish all channels.....");
               
                // user has selected to publish all channel in the /channels directory
                // lets publish all channel one by one that is
                // create InputStream object to pass to next method
                File f =
                    ResourceLoader.getResourceAsFile(
                            ChannelPublisher.class,
                            chanDefsLocation + "/");
                if (f.isDirectory()) {
                   
                    // Consider only files that end in .xml
                    class ChannelDefFileFilter implements FileFilter {
                        public boolean accept(File file) {
                            return file.getName().endsWith(".xml");
                        }
                    }
                    File[] files = f.listFiles(new ChannelDefFileFilter());
                   
                    for (int j = 0; j < files.length; j++) {
                        String name = files[j].getName();
                        // lets publish one at a time
                        try{
                            publisher.publishChannel(name);
                        }catch(Exception e){
                            // Add file name into exception so we will know which
                            // file has the problem.
                            throw new Exception("Unable to publish file: "+name,e);
                        }
                        System.out.println("Published channel " + name);
                    }
                }
            }
            System.out.println("Publishing finished.");
            System.exit(0);
        }catch(Exception e){
            // signal failure to ant and log
            log.error(e, e);
            throw e;
        }
    }

    /**
     * Sets up the system user for use during publishing.
     *
     */
    private void setupSystemUser()
    {
        systemUser = PersonFactory.createSystemPerson();
    }

    /**
     * Publishes the channel represented by the XML located in the file
     * represented by the passed in filename and returns the resultant
     * ChannelDefinition object.
     *
     * @param filename the name of a file containing the channel XML definition
     * @return org.jasig.portal.ChannelDefinition the published channel definition
     * @throws Exception
     */
    public ChannelDefinition publishChannel(String filename) throws Exception
    {
        ChannelInfo ci = getChannelInfo(filename);
        return publishChannel(ci);
    }

    /**
     * Publishes the channel represented by the XML accessed via the passed in
     * InputStream object and returns the resultant ChannelDefinition object.
     *
     * @param is and InputStream containing the channel XML definition
     * @return org.jasig.portal.ChannelDefinition the published channel definition
     * @throws Exception
     */
    public ChannelDefinition publishChannel(InputStream is) throws Exception
    {
        ChannelInfo ci = getChannelInfo(is);
        return publishChannel(ci);
    }

    /**
     * Publishes the channel represented by the passed in ChannelDefinition
     * object and returns the resultant ChannelDefinition object.
     *
     * @param ci
     * @return
     * @throws Exception
     */
    private ChannelDefinition publishChannel(ChannelInfo ci) throws Exception
    {

        if (ci == null)
            return null;

        try {
            if (ci.chanDef.getTypeId() != -1)
            {
                ChannelType type = crs.getChannelType(ci.chanDef.getTypeId());
                ci.chanDef.setJavaClass(type.getJavaClass());
            }
            crs.saveChannelDefinition(ci.chanDef);

            // Permission for everyone to subscribe to channel
            AuthorizationService authService = AuthorizationService.instance();
            String target = "CHAN_ID." + ci.chanDef.getId();
            IUpdatingPermissionManager upm = authService.newUpdatingPermissionManager(FRAMEWORK_OWNER);

            // Remove old permissions
            IPermission[] oldPermissions = upm.getPermissions(SUBSCRIBER_ACTIVITY, target);
            upm.removePermissions(oldPermissions);

          // Add new permissions for this channel based on both groups and users
          if (ci.groups != null) {
            IPermission[] newGroupPermissions = new IPermission[ci.groups.length];
            for (int j = 0; j < ci.groups.length; j++) {
                IAuthorizationPrincipal authPrincipal = authService.newPrincipal(ci.groups[j]);
                newGroupPermissions[j] = upm.newPermission(authPrincipal);
                newGroupPermissions[j].setType(GRANT_PERMISSION_TYPE);
                newGroupPermissions[j].setActivity(SUBSCRIBER_ACTIVITY);
                newGroupPermissions[j].setTarget(target);
            }
            upm.addPermissions(newGroupPermissions);
    }
          if (ci.users != null) {
              IPermission[] newUserPermissions = new IPermission[ci.users.length];
              for (int j=0; j < ci.users.length; j++) {
                    IAuthorizationPrincipal authPrincipal = authService.newPrincipal(ci.users[j]);
                    newUserPermissions[j] = upm.newPermission(authPrincipal);
                    newUserPermissions[j].setType(GRANT_PERMISSION_TYPE);
                    newUserPermissions[j].setActivity(SUBSCRIBER_ACTIVITY);
                    newUserPermissions[j].setTarget(target);
              }
              upm.addPermissions(newUserPermissions);
          }

            // Categories
            // First, remove channel from its categories
            ChannelCategory[] categories = crs.getParentCategories(ci.chanDef);
            for (int i = 0; i < categories.length; i++) {
                crs.removeChannelFromCategory(ci.chanDef, categories[i]);
            }
            // Now add channel to assigned categories
            if (ci.categories != null)
            {
                for (int k = 0; k < ci.categories.length; k++) {
                    crs.addChannelToCategory(ci.chanDef, ci.categories[k]);
                }
            }

            // Need to approve channel
            crs.approveChannelDefinition(ci.chanDef, systemUser, new Date());

        } catch (Exception e) {
            log.error("publishChannel() :: Exception while attempting to publish channel to database. Channel name = " + ci.chanDef.getName());
            throw e;
        }
        return ci.chanDef;
    }

    /**
     * Set up a DOM parser for handling the XML channel-definition data.
     *
     * @throws Exception
     */
    private void setupDomParser() throws Exception {
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setValidating(true);
            domParser = dbf.newDocumentBuilder();
            domParser.setEntityResolver(new ChannelDefDtdResolver());
            domParser.setErrorHandler(this);
        } catch (Exception e) {
            log.error( "setupDomParser() :: creating Dom Parser. ", e);
            throw e;
        }
    }

    /**
     * Populates and returns a ChannelInfo object based on the passed in
     * file name containing XML data structured according to the
     * channel-definition dtd.
     * 
     * @param chanDefFile
     * @return
     * @throws Exception
     */
    private ChannelInfo getChannelInfo(String chanDefFile) throws Exception {
        InputStream is =
            ResourceLoader.getResourceAsStream(
                ChannelPublisher.class,
                chanDefsLocation + "/" + chanDefFile);
        return getChannelInfo(is);
    }

    /**
     * Populates and returns a ChannelInfo object based on the input stream
     * containing XML data structured according to the channel-definition dtd.
     * 
     * @param is
     * @return
     * @throws Exception
     */
    private ChannelInfo getChannelInfo(InputStream is) throws Exception {
        ChannelInfo ci = new ChannelInfo();
        Document doc = null;

        // Build a DOM tree out of Channel_To_Publish.xml
        doc = domParser.parse(is);

        Element chanDefE = doc.getDocumentElement();
        String fname = getFname(chanDefE);

        // Use existing channel definition if it exists,
        // otherwise make a new one with a new ID
        ci.chanDef = crs.getChannelDefinition(fname);

        if (ci.chanDef != null && !mOverrideExisting)
        {
            log.error(
                "chanDef with fname "
                    + fname
                    + " already exists "
                    + "and override is false. Terminating publication.");
            return null;
        }

        if (ci.chanDef == null) {
            ci.chanDef = crs.newChannelDefinition();
        }

        for (Node param = chanDefE.getFirstChild(); param != null; param = param.getNextSibling()) {
            if (!(param instanceof Element))
                continue; // whitespace (typically \n) between tags
            Element pele = (Element) param;
            String tagname = pele.getTagName();
            String value = XML.getElementText(pele);

            // each tagname corresponds to an object data field
            if (tagname.equals("title"))
                ci.chanDef.setTitle(value);
            else if (tagname.equals("name"))
                ci.chanDef.setName(value);
            else if (tagname.equals("makeFNameAccessibleOnly"))
                ci.fNameAccessibleOnly = true;
            else if (tagname.equals("fname"))
                ci.chanDef.setFName(value);
            else if (tagname.equals("desc"))
                ci.chanDef.setDescription(value);
            else if (tagname.equals("type"))
                getType(ci, value);
            else if (tagname.equals("class"))
                ci.chanDef.setJavaClass(value);
            else if (tagname.equals("timeout"))
                ci.chanDef.setTimeout(Integer.parseInt(value));
            else if (tagname.equals("hasedit"))
                ci.chanDef.setEditable((value != null && value.equals("Y")) ? true : false);
            else if (tagname.equals("hashelp"))
                ci.chanDef.setHasHelp((value != null && value.equals("Y")) ? true : false);
            else if (tagname.equals("hasabout"))
                ci.chanDef.setHasAbout((value != null && value.equals("Y")) ? true : false);
                else if (tagname.equals("secure"))
                  ci.chanDef.setIsSecure((value != null && value.equals("Y")) ? true : false);               
            else if (mOnCommandLine && tagname.equals("categories"))
                getCategories(ci, pele);
            else if (mOnCommandLine && tagname.equals("groups"))
                getGroups(ci, pele);
            else if (mOnCommandLine && tagname.equals("users"))
                getUsers(ci, pele);
            else if (tagname.equals("parameters"))
                getParameters(ci, pele);

            ci.chanDef.setPublisherId(0); // system user
            ci.chanDef.setPublishDate(new Date());
        }
        if (ci.groups == null && ! mOnCommandLine)
            ci.groups = getAdminGroup();
        if (ci.categories == null && ! mOnCommandLine)
            ci.categories = getDefaultCategory(ci.fNameAccessibleOnly);
        return ci;
    }

    /**
     * Determines the functional name of the channel.
     *
     * @param chanDefE
     * @return
     * @throws Exception
     */
    private String getFname(Element chanDefE) throws Exception
    {
        String fname = null;
        for (Node n = chanDefE.getFirstChild();
            n != null;
            n = n.getNextSibling())
        {
            if (n.getNodeType() == Node.ELEMENT_NODE)
            {
                if (n.getNodeName().equals("fname"))
                {
                    fname = XML.getElementText((Element) n);
                }
            }
        }

        // Complain if we don't find an fname
        if (fname == null)
            throw new Exception("Missing required fname element");
        return fname;
    }

    /**
     * Translate the declared channel type name into a channel type id.
     * 
     * @param ci
     * @param value
     * @throws Exception
     */
    private void getType(ChannelInfo ci, String value)
        throws Exception
    {
        Integer typeId = (Integer) chanTypesNamesToIds.get(value);
        if (typeId != null)
        {
            ci.chanDef.setTypeId(typeId.intValue());
        }
        else
        {
            StringWriter sw = new StringWriter();
            PrintWriter pw = null;
            for (Iterator itr = chanTypesNamesToIds.keySet().iterator();
            itr.hasNext();)
            {
                if (pw == null)
                {
                    pw = new PrintWriter(sw);
                    pw.print("['");
                    pw.print((String) itr.next());
                    pw.print("'");
                }
                else
                {
                    pw.print(" | '");
                    pw.print((String) itr.next());
                    pw.print("'");
                }
            }
            pw.print("]");
            pw.flush();
            throw new Exception(
                "Invalid entry '"
                    + value
                    + "' for Channel Type. Must be one of "
                    + sw.toString());
        }
    }

    /**
     * Translate channel category names into category ids.
     *
     * @param ci The ChannelInfo object being populated.
     * @param pele The Element containing the category elements.
     * @throws Exception
     */
    private void getCategories(ChannelInfo ci, Element pele) throws Exception
    {
        NodeList anodes = pele.getElementsByTagName("category");
        if (anodes.getLength() != 0)
        {
            ci.categories = new ChannelCategory[anodes.getLength()];
            for (int j = 0; j < anodes.getLength(); j++)
            {
                Element anode = (Element) anodes.item(j);
                String catString = XML.getElementText(anode);
                // need to look up corresponding category id
                // ie: Applications = local.50
                //     Entertainment = local.51
                IEntityGroup cat = getGroup(catString, ChannelDefinition.class);

                if (cat != null)
                    ci.categories[j] = crs.getChannelCategory(cat.getKey());
                else
                    throw new Exception(
                        "Invalid entry '" + catString + "' for category.");
            }
        }
    }
   

    /**
     * Load the declared parameters.
     *
     * @param ci The ChannelInfo object being populated.
     * @param pele The Element containing the parameter elements.
     */
    private void getParameters(ChannelInfo ci, Element pele)
    {
        NodeList anodes = pele.getElementsByTagName("parameter");
        if (anodes.getLength() > 0)
        {
            for (int j = 0; j < anodes.getLength(); j++)
            {
                String pname = null;
                String pvalue = null;
                String povrd = null;
                String pdescr = null;
                Element anode = (Element) anodes.item(j);
                NodeList namenodes = anode.getElementsByTagName("name");
                if (namenodes.getLength() > 0)
                {
                    pname = XML.getElementText((Element) namenodes.item(0));
                }
                NodeList valuenodes = anode.getElementsByTagName("value");
                if (valuenodes.getLength() > 0)
                {
                    pvalue = XML.getElementText((Element) valuenodes.item(0));
                }
                NodeList descnodes = anode.getElementsByTagName("description");
                if (descnodes.getLength() > 0)
                {
                    pdescr = XML.getElementText((Element) descnodes.item(0));
                }
                NodeList ovrdnodes = anode.getElementsByTagName("ovrd");
                if (ovrdnodes.getLength() > 0)
                {
                    povrd = XML.getElementText((Element) ovrdnodes.item(0));
                }
                ChannelParameter chanParam =
                    new ChannelParameter(pname, pvalue, RDBMServices.dbFlag(povrd));
                chanParam.setDescription(pdescr);
                ci.chanDef.addParameter(chanParam);
            }
        }
    }

    /**
     * Translate access group names into group ids.
     *
     * @param ci The ChannelInfo object being populated.
     * @param pele The Element containing the group elements.
     * @throws Exception
     */
    private void getGroups(ChannelInfo ci, Element pele) throws Exception
    {
        NodeList anodes = pele.getElementsByTagName("group");
        if (anodes.getLength() != 0)
        {
            ci.groups = new IEntityGroup[anodes.getLength()];
            for (int j = 0; j < anodes.getLength(); j++)
            {
                Element anode = (Element) anodes.item(j);
                String groupStr = XML.getElementText(anode);
                // need to look up corresponding group id
                // ie: Everyone = local.0
                //     Developers = local.4
                IEntityGroup group = getGroup(groupStr, IPerson.class);

                if (group != null)
                    ci.groups[j] = group;
                else
                    throw new Exception(
                        "Invalid entry '" + groupStr + "' for group.");
            }
        }
    }

    /**
     * Translate access user ids into the user entity objects.
     *
     * @param ci The ChannelInfo object being populated.
     * @param pele The Element containing the user elements.
     * @throws Exception
     */
    private void getUsers(ChannelInfo ci, Element pele) throws Exception
    {
        NodeList anodes = pele.getElementsByTagName("user");
        if (anodes.getLength() != 0)
        {
            ci.users = new IEntity[anodes.getLength()];
            for (int j = 0; j < anodes.getLength(); j++)
            {
                Element anode = (Element) anodes.item(j);
                String userStr = XML.getElementText(anode);
                // need to look up corresponding user
                IEntity user = GroupService.getEntity(userStr, IPerson.class);

                if (user != null)
                    ci.users[j] = user;
                else
                    throw new Exception(
                        "Invalid entry '" + userStr + "' for user.");
            }
        }
    }

    /**
     * Gets the portal administrators group.
     */
    private IEntityGroup[] getAdminGroup() throws Exception
    {
        String dg = GroupService.PORTAL_ADMINISTRATORS;
        IEntityGroup e = GroupService.getDistinguishedGroup(dg);
        return new IEntityGroup[] { e };
    }

    /**
     * Obtains an auto-publish category located in the root category or creates
     * if not found.
     */
    private ChannelCategory[] getDefaultCategory(boolean fNameAccessibleOnly)
        throws Exception
    {
        ChannelCategory rootCat = crs.getTopLevelChannelCategory();

        if (fNameAccessibleOnly)
            return new ChannelCategory[] { rootCat };

        ChannelCategory[] topCats = crs.getChildCategories(rootCat);
        ChannelCategory autoCat = null;

        for (int i = 0; i < topCats.length; i++)
        {
            if (topCats[i].getName().equals(Constants.AUTO_PUBLISH_CATEGORY))
            {
                autoCat = topCats[i];
                break;
            }
        }
        if (autoCat == null) // target category not created. create.
        {
            autoCat =
                crs.newChannelCategory(
                    Constants.AUTO_PUBLISH_CATEGORY,
                    "Holds Auto-published " + "Channels.",
                    "system");
            crs.addCategoryToCategory(autoCat, rootCat);
        }
        return new ChannelCategory[] { autoCat };
    }

    /**
     * Attempts to determine group key based on a group name or fully qualifed
     * group key.
     * @param groupName a <code>String</code> value
     * @param entityType the kind of entity the group contains
     * @return a group key
     */
    private static IEntityGroup getGroup(String groupName, Class entityType) throws Exception {
        IEntityGroup group = null;
        EntityIdentifier[] groups = GroupService.searchForGroups(groupName, IGroupConstants.IS, entityType);
        if (groups != null && groups.length > 0) {
            group = GroupService.findGroup(groups[0].getKey());
        } else {
            // An actual group key might be specified, so try looking up group directly
            group = GroupService.findGroup(groupName);
        }
        return group;
    }

    /**
     * Load the lookup table to translate channel type names into ids.
     *
     * @throws Exception
     */
    private void initChanTypeMap()
    throws Exception
    {
        if (chanTypesNamesToIds == null) {
            chanTypesNamesToIds = new HashMap();
            chanTypesNamesToIds.put("Custom", new Integer(-1));

            ChannelType[] types = crs.getChannelTypes();
            for (int i = 0; i < types.length; i++)
                chanTypesNamesToIds.put( types[i].getName(),
                new Integer(types[i].getId()));
        }
    }
 
    private static class ChannelInfo
    {
        ChannelDefinition chanDef;
        IEntityGroup[] groups;
        ChannelCategory[] categories;
        boolean fNameAccessibleOnly = false;
      IEntity[] users;
    }

    private ChannelPublisher(boolean onCommandLine) throws Exception
    {
        mOnCommandLine = onCommandLine;
        // initialize channel registry store
        crs = ChannelRegistryStoreFactory.getChannelRegistryStoreImpl();
        // read in channel types
        initChanTypeMap();

        // create IPerson object for the portal's system user
        setupSystemUser();
        // setup DOM Parser with dtd validation
        setupDomParser();
    }

    public void setOverride(boolean b)
    {
        mOverrideExisting = b;
    }

    public static ChannelPublisher getCommandLineInstance() throws Exception
    {
        ChannelPublisher publisher = new ChannelPublisher(true);
        publisher.setOverride(true);
        return publisher;
    }

    public static ChannelPublisher getChannelArchiveInstance() throws Exception
    {
        return new ChannelPublisher(false);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.xml.sax.ErrorHandler#warning(org.xml.sax.SAXParseException)
     */
    public void warning(SAXParseException arg0) throws SAXException
    {
        if (log.isInfoEnabled())
            log.info("Warning occurred while parsing channel definition.",
                            arg0);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.xml.sax.ErrorHandler#error(org.xml.sax.SAXParseException)
     */
    public void error(SAXParseException arg0) throws SAXException
    {
        throw new SAXException(
                "Error occurred while parsing channel definition.", arg0);
    }

    /*
     * (non-Javadoc)
     *
     * @see org.xml.sax.ErrorHandler#fatalError(org.xml.sax.SAXParseException)
     */
    public void fatalError(SAXParseException arg0) throws SAXException
    {
        throw new SAXException(
                "Fatal Error occurred while parsing channel definition.", arg0);
    }
}
TOP

Related Classes of org.jasig.portal.tools.chanpub.ChannelPublisher

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.