Package com.salas.bb.service.sync

Source Code of com.salas.bb.service.sync.SyncOut$SyncOutStats

// BlogBridge -- RSS feed reader, manager, and web based service
// Copyright (C) 2002-2006 by R. Pito Salas
//
// This program is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software Foundation;
// either version 2 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with this program;
// if not, write to the Free Software Foundation, Inc., 59 Temple Place,
// Suite 330, Boston, MA 02111-1307 USA
//
// Contact: R. Pito Salas
// mailto:pitosalas@users.sourceforge.net
// More information: about BlogBridge
// http://www.blogbridge.com
// http://sourceforge.net/projects/blogbridge
//
// $Id: SyncOut.java,v 1.47 2008/02/28 15:59:50 spyromus Exp $
//

package com.salas.bb.service.sync;

import com.salas.bb.core.FeedDisplayModeManager;
import com.salas.bb.core.GlobalController;
import com.salas.bb.core.GlobalModel;
import com.salas.bb.domain.*;
import com.salas.bb.domain.prefs.StarzPreferences;
import com.salas.bb.domain.prefs.UserPreferences;
import com.salas.bb.imageblocker.ImageBlocker;
import com.salas.bb.plugins.Manager;
import com.salas.bb.sentiments.SentimentsConfig;
import com.salas.bb.service.ServerService;
import com.salas.bb.service.ServerServiceException;
import com.salas.bb.service.ServicePreferences;
import com.salas.bb.twitter.TwitterPreferences;
import com.salas.bb.utils.StringUtils;
import com.salas.bb.utils.i18n.Strings;
import com.salas.bb.utils.opml.Converter;
import com.salas.bb.utils.uif.UifUtilities;
import com.salas.bb.views.settings.FeedRenderingSettings;
import com.salas.bb.views.settings.RenderingSettingsNames;
import com.salas.bbutilities.opml.export.Exporter;
import com.salas.bbutilities.opml.objects.OPMLGuideSet;
import com.salas.bbutilities.opml.utils.Transformation;
import org.jdom.Document;

import java.awt.*;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.MessageFormat;
import java.util.*;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Outgoing synchronization.
*/
public class SyncOut extends AbstractSynchronization
{
    private static final Logger LOG = Logger.getLogger(SyncOut.class.getName());
    private static final String THREAD_NAME_PING = "Ping RL";

    private static final MessageFormat RL_PUB_URL =
        new MessageFormat("http://www.blogbridge.com/rl/{0,number,#}/{1}.opml");

    /**
     * Creates outgoing synchronization module.
     *
     * @param aModel model to operate.
     */
    public SyncOut(GlobalModel aModel)
    {
        super(aModel);
    }

    /**
     * Performs the step-by-step synchronization and collects stats.
     *
     * @param progress listener to notify.
     * @param aEmail            email of user account.
     * @param aPassword         password of user account.
     *
     * @return statistics.
     */
    protected Stats doSynchronization(IProgressListener progress, String aEmail,
                                      String aPassword)
    {
        SyncOutStats stats = new SyncOutStats();

        try
        {
            // save feeds
            if (servicePreferences.isSyncFeeds())
            {
                if (progress != null) progress.processStep(Strings.message("service.sync.out.saving.guides.and.feeds"));
                storeFeeds(aEmail, aPassword, stats);
                if (progress != null) progress.processStepCompleted();
            }

            // initiate background ping
            pingGuides();

            // save preferences
            if (servicePreferences.isSyncPreferences())
            {
                if (progress != null) progress.processStep(Strings.message("service.sync.out.saving.preferences"));
                storePreferences(aEmail, aPassword, stats);
                if (progress != null) progress.processStepCompleted();
            }

            // if call was successful put appropriate status in preferences
            servicePreferences.setLastSyncOutStatus(ServicePreferences.SYNC_STATUS_SUCCESS);
            servicePreferences.setLastSyncOutFeedsCount(model.getGuidesSet().countFeeds());
        } catch (ServerServiceException e1)
        {
            // synchronization errored out for some reason
            servicePreferences.setLastSyncOutStatus(ServicePreferences.SYNC_STATUS_FAILURE);

            // report only if servervice exception was caused by another error
            if (e1.getCause() != null)
            {
                LOG.log(Level.SEVERE, Strings.error("sync.error.during.sync.out"), e1);
                stats.registerFailure(null);
            } else
            {
                stats.registerFailure(e1.getMessage());
            }
        }

        servicePreferences.setLastSyncOutDate(new Date());

        return stats;
    }

    /**
     * Saves preferences to the service.
     *
     * @param aEmail    account email.
     * @param aPassword account password.
     * @param aStats    stats to fill.
     *
     * @throws ServerServiceException in case of service error.
     */
    private void storePreferences(String aEmail, String aPassword, SyncOutStats aStats)
        throws ServerServiceException
    {
        final Hashtable<String, Object> prefs = new Hashtable<String, Object>();

        // image blocker expressions
        String expressions = StringUtils.join(ImageBlocker.getExpressions().iterator(), "\n");
        prefs.put(ImageBlocker.KEY, StringUtils.toUTF8(expressions));

        SentimentsConfig.syncOut(prefs);
       
        storeGeneralPreferences(prefs);
        storeGuidesPreferences(prefs);
        storeFeedsPreferences(prefs);
        storeArticlesPreferences(prefs);
        storeTagsPreferences(prefs);
        storeReadingListsPreferences(prefs);
        storeAdvancedPreferences(prefs);
        storeWhatsHotPreferences(prefs);
        storeTwitterPreferences(prefs);
        Manager.storeState(prefs);

        // Save current time to record on the service
        setLong(prefs, "timestamp", System.currentTimeMillis());

        ServerService.syncStorePrefs(aEmail, aPassword, prefs);

        // record number of saved preferences
        aStats.savedPreferences = prefs.size();
    }

    /**
     * Saves guides and feeds to the service.
     *
     * @param aEmail    account email.
     * @param aPassword account password.
     * @param aStats    stats to fill.
     *
     * @throws ServerServiceException in case of service error.
     */
    private void storeFeeds(String aEmail, String aPassword, SyncOutStats aStats)
        throws ServerServiceException
    {
        // export information about guides
        GuidesSet guidesSet = model.getGuidesSet();

        // Calculate feed hashes basing on their present XML URLs for later updates upon successful completion
        Map<DirectFeed, Integer> feedHashes = calculateFeedHashes(guidesSet);

        OPMLGuideSet opmlSet = Converter.convertToOPML(guidesSet, "BlogBridge Feeds");
        Document doc = new Exporter(true).export(opmlSet);

        // prepare parameters for server call
        String opml = Transformation.documentToString(doc);

        int userId = ServerService.syncStore(aEmail, aPassword, opml);
        updatePublishedListsURLs(guidesSet, userId);

        // Update synchronization times
        guidesSet.onSyncOutCompletion();

        // Update feed with hashes
        updateFeedsWithHashes(feedHashes);

        // Remove all keys of deleted feeds as we just transfered our feeds list to the service
        GlobalController.SINGLETON.getDeletedFeedsRepository().purge();

        // Count saved guides/feeds
        StandardGuide[] guides = guidesSet.getStandardGuides(null);
        aStats.savedGuides = guides.length;
        aStats.savedFeeds = countFeeds(guides);
    }

    /**
     * Updates feeds with URL hashes.
     *
     * @param hashes hashes.
     */
    static void updateFeedsWithHashes(Map<DirectFeed, Integer> hashes)
    {
        for (Map.Entry<DirectFeed, Integer> en : hashes.entrySet())
        {
            DirectFeed feed = en.getKey();
            int hash = en.getValue();

            feed.setSyncHash(hash);
        }
    }

    /**
     * Calculates hashes for all direct feeds in the set. The hash is calculated from the
     * present XML URL.
     *
     * @param set set to parse.
     *
     * @return hashes.
     */
    static Map<DirectFeed, Integer> calculateFeedHashes(GuidesSet set)
    {
        Map<DirectFeed, Integer> hashes = new IdentityHashMap<DirectFeed, Integer>();

        List<IFeed> feeds = set.getFeeds();
        for (IFeed feed : feeds)
        {
            if (feed instanceof DirectFeed)
            {
                DirectFeed dfeed = (DirectFeed)feed;
                int hash = dfeed.calcSyncHash();

                hashes.put(dfeed, hash);
            }
        }

        return hashes;
    }

    /**
     * Updates URLs of all published guides.
     *
     * @param set       guides set to update.
     * @param userId    user ID.
     */
    private void updatePublishedListsURLs(GuidesSet set, int userId)
    {
        long publishingTime = System.currentTimeMillis();

        int count = set.getGuidesCount();
        for (int i = 0; i < count; i++)
        {
            IGuide guide = set.getGuideAt(i);
            String publishingTitle = guide.getPublishingTitle();
            if (guide.isPublishingEnabled() && StringUtils.isNotEmpty(publishingTitle))
            {
                String url = RL_PUB_URL.format(new Object[] {
                    userId,
                    StringUtils.encodeForURL(publishingTitle)
                });

                guide.setPublishingURL(url);
                guide.setLastPublishingTime(publishingTime);
            }
        }
    }

    /**
     * Returns the message to be reported on synchronization start.
     *
     * @return message.
     */
    protected String getProcessStartMessage()
    {
        return prepareProcessStartMessage(
            Strings.message("service.sync.message.synchronizing"),
            Strings.message("service.sync.message.preferences"),
            Strings.message("service.sync.message.guides.and.feeds"),
            Strings.message("service.sync.message.with.blogbridge.service")
        );
    }

    /**
     * Returns number of feeds in guides total.
     *
     * @param guides guides list.
     *
     * @return total number of feeds.
     */
    private static int countFeeds(IGuide[] guides)
    {
        int cnt = 0;

        for (IGuide guide : guides) cnt += guide.getFeedsCount();

        return cnt;
    }

    /**
     * Simple statistics holder.
     */
    public static class SyncOutStats extends Stats
    {
        private int savedGuides      = -1;
        private int savedFeeds       = -1;
        private int savedPreferences = -1;

        /**
         * Returns custom text to be told if not failed.
         *
         * @return text.
         */
        protected String getCustomText()
        {
            StringBuffer buf = new StringBuffer();

            if (savedGuides > 0) buf.append(MessageFormat.format(
                Strings.message("service.sync.out.status.guides.saved"),
                savedGuides));
            if (savedFeeds > 0) buf.append(MessageFormat.format(
                Strings.message("service.sync.out.status.feeds.saved"),
                savedFeeds));
            if (savedPreferences > 0) buf.append(MessageFormat.format(
                Strings.message("service.sync.out.status.preference.saved"),
                savedPreferences));

            return buf.toString();
        }
    }

    // ---------------------------------------------------------------------------------------------
    // Preferences storing
    // ---------------------------------------------------------------------------------------------

    /**
     * Stores general preferences.
     *
     * @param prefs preferences map.
     */
    private void storeGeneralPreferences(Map prefs)
    {
        UserPreferences up = model.getUserPreferences();

        setBoolean(prefs, UserPreferences.PROP_CHECKING_FOR_UPDATES_ON_STARTUP,
            up.isCheckingForUpdatesOnStartup());
// Disabled as we don't like what happens when synchronizing fonts across platforms
//        setFont(prefs, RenderingSettingsNames.MAIN_CONTENT_FONT, frs.getMainContentFont());
        setBoolean(prefs, UserPreferences.PROP_SHOW_TOOLBAR, up.isShowToolbar());

        // Behaviour
        setBoolean(prefs, UserPreferences.PROP_MARK_READ_WHEN_CHANGING_CHANNELS,
            up.isMarkReadWhenChangingChannels());
        setBoolean(prefs, UserPreferences.PROP_MARK_READ_WHEN_CHANGING_GUIDES,
            up.isMarkReadWhenChangingGuides());
        setBoolean(prefs, UserPreferences.PROP_MARK_READ_AFTER_DELAY,
            up.isMarkReadAfterDelay());
        setInt(prefs, UserPreferences.PROP_MARK_READ_AFTER_SECONDS,
            up.getMarkReadAfterSeconds());

        // Updates and Cleanups
        setInt(prefs, UserPreferences.PROP_RSS_POLL_MIN,
            up.getRssPollInterval());
        setInt(prefs, UserPreferences.PROP_PURGE_COUNT,
            up.getPurgeCount());
        setBoolean(prefs, UserPreferences.PROP_PRESERVE_UNREAD,
            up.isPreserveUnread());
    }

    /**
     * Stores guides preferences.
     *
     * @param prefs preferences map.
     */
    private void storeGuidesPreferences(Map prefs)
    {
        UserPreferences up = model.getUserPreferences();
        FeedRenderingSettings frs = model.getGlobalRenderingSettings();

        setBoolean(prefs, UserPreferences.PROP_PING_ON_RL_PUBLICATION, up.isPingOnReadingListPublication());
        setString(prefs, UserPreferences.PROP_PING_ON_RL_PUBLICATION_URL, up.getPingOnReadingListPublicationURL());

        setBoolean(prefs, RenderingSettingsNames.IS_BIG_ICON_IN_GUIDES, frs.isBigIconInGuides());
        setBoolean(prefs, "showUnreadInGuides", frs.isShowUnreadInGuides());
        setBoolean(prefs, RenderingSettingsNames.IS_ICON_IN_GUIDES_SHOWING,
            frs.isShowIconInGuides());
        setBoolean(prefs, RenderingSettingsNames.IS_TEXT_IN_GUIDES_SHOWING,
            frs.isShowTextInGuides());

        setInt(prefs, UserPreferences.PROP_GUIDE_SELECTION_MODE, up.getGuideSelectionMode());
    }

    /**
     * Stores feeds preferences.
     *
     * @param prefs preferences map.
     */
    private void storeFeedsPreferences(Map prefs)
    {
        UserPreferences up = model.getUserPreferences();
        FeedRenderingSettings frs = model.getGlobalRenderingSettings();

        setBoolean(prefs, "showStarz", frs.isShowStarz());
        setBoolean(prefs, "showUnreadInFeeds", frs.isShowUnreadInFeeds());
        setBoolean(prefs, "showActivityChart", frs.isShowActivityChart());

        setFilterColor(prefs, FeedClass.DISABLED);
        setFilterColor(prefs, FeedClass.INVALID);
        setFilterColor(prefs, FeedClass.LOW_RATED);
        setFilterColor(prefs, FeedClass.READ);
        setFilterColor(prefs, FeedClass.UNDISCOVERED);

        setBoolean(prefs, UserPreferences.PROP_SORTING_ENABLED, up.isSortingEnabled());
        setInt(prefs, UserPreferences.PROP_SORT_BY_CLASS_1, up.getSortByClass1());
        setInt(prefs, UserPreferences.PROP_SORT_BY_CLASS_2, up.getSortByClass2());
        setBoolean(prefs, UserPreferences.PROP_REVERSED_SORT_BY_CLASS_1,
            up.isReversedSortByClass1());
        setBoolean(prefs, UserPreferences.PROP_REVERSED_SORT_BY_CLASS_2,
            up.isReversedSortByClass2());
    }

    /**
     * Stores articles preferences.
     *
     * @param prefs preferences map.
     */
    private void storeArticlesPreferences(Map prefs)
    {
        UserPreferences up = model.getUserPreferences();
        FeedRenderingSettings frs = model.getGlobalRenderingSettings();

        setBoolean(prefs, "groupingEnabled", frs.isGroupingEnabled());
        setBoolean(prefs, "suppressingOlderThan", frs.isSuppressingOlderThan());
        setBoolean(prefs, "displayingFullTitles", frs.isDisplayingFullTitles());
        setBoolean(prefs, "sortingAscending", frs.isSortingAscending());
        setInt(prefs, "suppressOlderThan", frs.getSuppressOlderThan());

        setBoolean(prefs, UserPreferences.PROP_COPY_LINKS_IN_HREF_FORMAT,
            up.isCopyLinksInHrefFormat());
        setBoolean(prefs, "showEmptyGroups", frs.isShowEmptyGroups());
        setBoolean(prefs, UserPreferences.PROP_BROWSE_ON_DBL_CLICK, up.isBrowseOnDblClick());

        setBoolean(prefs, UserPreferences.PROP_AUTO_EXPAND_MINI, up.isAutoExpandMini());
       
        up.getViewModePreferences().store(prefs);
    }

    /**
     * Stores tags preferences.
     *
     * @param prefs preferences map.
     */
    private void storeTagsPreferences(Map prefs)
    {
        UserPreferences up = model.getUserPreferences();

        setInt(prefs, UserPreferences.PROP_TAGS_STORAGE, up.getTagsStorage());
        setString(prefs, UserPreferences.PROP_TAGS_DELICIOUS_USER, up.getTagsDeliciousUser());
        setString(prefs, UserPreferences.PROP_TAGS_DELICIOUS_PASSWORD,
            up.getTagsDeliciousPassword());
        setBoolean(prefs, UserPreferences.PROP_TAGS_AUTOFETCH, up.isTagsAutoFetch());
        setBoolean(prefs, UserPreferences.PROP_PIN_TAGGING, up.isPinTagging());
        setString(prefs, UserPreferences.PROP_PIN_TAGS, up.getPinTags());
    }

    /**
     * Stores reading lists preferences.
     *
     * @param prefs preferences map.
     */
    private void storeReadingListsPreferences(Map prefs)
    {
        UserPreferences up = model.getUserPreferences();

        setLong(prefs, UserPreferences.PROP_READING_LIST_UPDATE_PERIOD,
            up.getReadingListUpdatePeriod());
        setInt(prefs, UserPreferences.PROP_ON_READING_LIST_UPDATE_ACTIONS,
            up.getOnReadingListUpdateActions());
        setBoolean(prefs, UserPreferences.PROP_UPDATE_FEEDS,
            up.isUpdateFeeds());
        setBoolean(prefs, UserPreferences.PROP_UPDATE_READING_LISTS,
            up.isUpdateReadingLists());
    }

    /**
     * Stores advanced preferences.
     *
     * @param prefs preferences map.
     */
    private void storeAdvancedPreferences(Map prefs)
    {
        UserPreferences up = model.getUserPreferences();
        StarzPreferences sp = model.getStarzPreferences();

        setInt(prefs, UserPreferences.PROP_FEED_SELECTION_DELAY, up.getFeedSelectionDelay());
        setBoolean(prefs, UserPreferences.PROP_AA_TEXT, up.isAntiAliasText());

        setInt(prefs, StarzPreferences.PROP_TOP_ACTIVITY, sp.getTopActivity());
        setInt(prefs, StarzPreferences.PROP_TOP_HIGHLIGHTS, sp.getTopHighlights());

        setBoolean(prefs, UserPreferences.PROP_SHOW_TOOLBAR_LABELS, up.isShowToolbarLabels());
        setBoolean(prefs, UserPreferences.PROP_SHOW_UNREAD_BUTTON_MENU, up.isShowUnreadButtonMenu());
        setInt(prefs, UserPreferences.PROP_FEED_IMPORT_LIMIT, up.getFeedImportLimit());
    }

    /**
     * Stores what's hot preferences.
     *
     * @param prefs preferences map.
     */
    private void storeWhatsHotPreferences(Map prefs)
    {
        UserPreferences up = model.getUserPreferences();
        setString(prefs, UserPreferences.PROP_WH_IGNORE, up.getWhIgnore());
        setBoolean(prefs, UserPreferences.PROP_WH_NOSELFLINKS, up.isWhNoSelfLinks());
        setBoolean(prefs, UserPreferences.PROP_WH_SUPPRESS_SAME_SOURCE_LINKS, up.isWhSuppressSameSourceLinks());
        setString(prefs, UserPreferences.PROP_WH_TARGET_GUIDE, up.getWhTargetGuide());
        setLong(prefs, UserPreferences.PROP_WH_SETTINGS_CHANGE_TIME, up.getWhSettingsChangeTime());
    }

    /**
     * Stores Twitter preferences.
     *
     * @param prefs prefs.
     */
    private void storeTwitterPreferences(Map prefs)
    {
        TwitterPreferences tp = model.getUserPreferences().getTwitterPreferences();
        setBoolean(prefs, TwitterPreferences.PROP_TWITTER_ENABLED, tp.isEnabled());
        setString(prefs, TwitterPreferences.PROP_TWITTER_SCREEN_NAME, tp.getScreenName());
        setString(prefs, TwitterPreferences.PROP_TWITTER_ACCESS_TOKEN, tp.getAccessToken());
        setString(prefs, TwitterPreferences.PROP_TWITTER_TOKEN_SECRET, tp.getTokenSecret());
        setBoolean(prefs, TwitterPreferences.PROP_TWITTER_PROFILE_PICS, tp.isProfilePics());
        setBoolean(prefs, TwitterPreferences.PROP_TWITTER_PASTE_LINK, tp.isPasteLink());
    }

    /**
     * Saves boolean to preferences map.
     *
     * @param prefs     preferences map.
     * @param name      property name.
     * @param value     value.
     */
    public static void setBoolean(Map prefs, String name, boolean value)
    {
        setString(prefs, name, Boolean.toString(value));
    }

    /**
     * Saves integer property to preferences map.
     *
     * @param prefs     preferences map.
     * @param name      property name.
     * @param value     value.
     */
    public static void setInt(Map prefs, String name, int value)
    {
        setString(prefs, name, Integer.toString(value));
    }

    private static void setLong(Map prefs, String name, long value)
    {
        setString(prefs, name, Long.toString(value));
    }

    private static void setFilterColor(Map prefs, int feedClass)
    {
        FeedDisplayModeManager fdmm = FeedDisplayModeManager.getInstance();
        Color color = fdmm.getColor(feedClass);
        setString(prefs, "cdmm." + feedClass, UifUtilities.colorToHex(color));
    }

    public static void setString(Map prefs, String name, String value)
    {
        byte[] bytes = StringUtils.toUTF8(value);
        prefs.put(name, bytes == null ? new byte[0] : bytes);
    }

    // -----------------------------------------------------------------------------------------------------------------
    // Pinging
    // -----------------------------------------------------------------------------------------------------------------

    /**
     * Ping URL with all published reading lists.
     */
    private void pingGuides()
    {
        Thread thPing = new Thread(new Runnable()
        {
            public void run()
            {
                // Ping URL
                GlobalModel model = GlobalController.SINGLETON.getModel();
                UserPreferences prefs = model.getUserPreferences();
                String url = prefs.getPingOnReadingListPublicationURL().trim();
                if (prefs.isPingOnReadingListPublication() && url.length() > 0 && url.indexOf("%u") != -1)
                {
                    IGuide[] publishedGuides = collectGuides(model.getGuidesSet());
                    pingGuides(publishedGuides, url);
                }
            }
        }, THREAD_NAME_PING);

        thPing.start();
    }

    /**
     * Pings all guides one by one.
     *
     * @param guides    guide list.
     * @param url       URL to ping with guide publication URL included.
     */
    private void pingGuides(IGuide[] guides, String url)
    {
        for (IGuide guide : guides)
        {
            String realURL = url.replaceAll("%u", guide.getPublishingURL());
            try
            {
                ping(new URL(realURL));
            } catch (Throwable e)
            {
                LOG.log(Level.WARNING, Strings.error("sync.failed.to.ping.reading.list.service"), e);
            }
        }
    }

    /**
     * Pings the URL.
     *
     * @param url url to ping.
     *
     * @throws java.io.IOException I/O exception.
     */
    private void ping(URL url) throws IOException
    {
        // Read one byte to make sure that we connected
        InputStream stream = url.openStream();

        //noinspection ResultOfMethodCallIgnored
        stream.read();
        stream.close();
    }

    /**
     * Returns all guides which have publication flag set and the publication URL available.
     *
     * @param set guides set.
     *
     * @return list of guides.
     */
    private IGuide[] collectGuides(GuidesSet set)
    {
        StandardGuide[] guides = set.getStandardGuides(null);
        java.util.List<StandardGuide> rl = new ArrayList<StandardGuide>();
        for (StandardGuide guide : guides)
        {
            String url = guide.getPublishingURL();
            if (guide.isPublishingEnabled() && url != null && url.trim().length() > 0) rl.add(guide);
        }

        return rl.toArray(new IGuide[rl.size()]);
    }
}
TOP

Related Classes of com.salas.bb.service.sync.SyncOut$SyncOutStats

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.