fr.jmmc.smprsc.data.stub.StubMetaData.java Source code

Java tutorial

Introduction

Here is the source code for fr.jmmc.smprsc.data.stub.StubMetaData.java

Source

/*******************************************************************************
 *          AppLauncher project ( http://www.jmmc.fr/applauncher )
 *******************************************************************************
 * Copyright (c) 2014, CNRS. All rights reserved.
 *
 * This file is part of AppLauncher.
 *
 * AppLauncher 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, version 3.
 *
 * AppLauncher 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
 * AppLauncher. If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/
package fr.jmmc.smprsc.data.stub;

import fr.jmmc.jmcs.data.preference.Preferences;
import fr.jmmc.jmcs.data.preference.PreferencesException;
import fr.jmmc.jmcs.gui.util.SwingUtils;
import fr.jmmc.jmcs.util.jaxb.JAXBFactory;
import fr.jmmc.jmcs.util.jaxb.JAXBUtils;
import fr.jmmc.jmcs.util.jaxb.XmlBindException;
import fr.jmmc.jmcs.network.http.Http;
import fr.jmmc.jmcs.network.http.PostQueryProcessor;
import fr.jmmc.jmcs.network.interop.SampMetaData;
import fr.jmmc.jmcs.util.FileUtils;
import fr.jmmc.jmcs.util.ResourceUtils;
import fr.jmmc.smprsc.data.stub.model.SampStub;
import java.io.IOException;
import java.io.StringWriter;
import java.net.URI;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.ImageIcon;
import org.apache.commons.httpclient.methods.PostMethod;
import org.astrogrid.samp.Metadata;
import org.astrogrid.samp.Subscriptions;
import fr.jmmc.jmcs.util.concurrent.ThreadExecutors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Real SAMP application meta data older, that can report to JMMC central registry if not referenced yet.
 *
 * @author Sylvain LAFRASSE.
 */
public class StubMetaData {

    // Constants
    /** Package name for JAXB generated code */
    private final static String STUB_DATA_MODEL_JAXB_PATH = "fr.jmmc.smprsc.data.stub.model";
    /** Base URL of the JMMC SAMP application meta data repository */
    private final static String REGISTRY_BASE_URL = "http://jmmc.fr/~smprun/stubs/";//"http://jmmc.fr/~lafrasse/stubs/";
    /** JMMC SAMP application meta data repository submission form name */
    private final static String REGISTRY_SUBMISSION_FORM_NAME = "push.php";
    /** JMMC SAMP application meta data repository directory containing all stubs definition files */
    private static final String SAMP_STUB_REGISTRY_DIRECTORY = "registry/";
    /** Resource directory containing all SAMP application files */
    private final static String SAMP_STUB_RESOURCE_DIRECTORY = "fr/jmmc/smprsc/registry/";
    /** File extension of the JMMC SAMP application meta data file format */
    private final static String SAMP_STUB_FILE_EXTENSION = ".xml";
    /** Application icon files extension */
    private final static String SAMP_STUB_ICON_FILE_EXTENSION = ".png";
    // Statics
    /** Logger */
    private final static Logger _logger = LoggerFactory.getLogger(StubMetaData.class.getName());
    /** JAXB initialization */
    private final static JAXBFactory _jaxbFactory = JAXBFactory.getInstance(STUB_DATA_MODEL_JAXB_PATH);
    /** Loaded SampStub cache */
    private final static Map<String, SampStub> _cachedSampStubs = new HashMap<String, SampStub>();
    // Members
    /** SAMP application meta data container */
    private final SampStub _data = new SampStub();
    /** Real application exact name */
    private final String _applicationName;
    /** Cleaned application exact name */
    private final String _applicationId;
    /** Real application SAMP meta data */
    private final Metadata _sampMetaData;
    /** Real application SAMP mTypes */
    private final Subscriptions _sampSubscriptions;

    /**
     * Constructor.
     *
     * @param metadata SAMP Meta data
     * @param subscriptions SAMP mTypes
     */
    public StubMetaData(Metadata metadata, Subscriptions subscriptions) {

        _logger.debug("Serializing SAMP application meta-data.");

        _applicationName = metadata.getName();
        _applicationId = FileUtils.cleanupFileName(_applicationName);
        _data.setUid(_applicationId);
        _sampMetaData = metadata; // Should clone it instead, but clone() is not implemented in jSAMP
        _sampSubscriptions = subscriptions;
    }

    public static SampStub retrieveSampStubForApplication(String applicationName) {

        String applicationId = FileUtils.cleanupFileName(applicationName);
        SampStub sampStub = _cachedSampStubs.get(applicationId);
        if (sampStub == null) {

            sampStub = loadSampStubForApplication(applicationId);
            _cachedSampStubs.put(applicationId, sampStub);
        }

        return sampStub;
    }

    /**
     * Try to load embedded icon for given application name.
     *
     * @param applicationName the application name of the sought icon.
     * @return the icon if found, null otherwise.
     */
    public static ImageIcon getEmbeddedApplicationIcon(String applicationName) {

        ImageIcon icon = null;

        // Forge icon resource path
        final String applicationId = FileUtils.cleanupFileName(applicationName);
        final String iconResourcePath = SAMP_STUB_RESOURCE_DIRECTORY + applicationId
                + SAMP_STUB_ICON_FILE_EXTENSION;

        try {
            // Try to load application icon resource
            final URL fileURL = ResourceUtils.getResource(iconResourcePath);
            if (fileURL != null) {
                icon = new ImageIcon(fileURL);
            }
        } catch (IllegalStateException ise) {
            _logger.warn("Could not find '{}' embedded icon.", applicationName);
        }

        return icon;
    }

    /**
     * @return application complete description as a String.
     */
    public String getApplicationDescription() {
        serializeMetaData(null, null); // Report without further data
        return marshallApplicationDescription();
    }

    /**
     * Upload application complete description to JMMC central repository (only if not known yet).
     *
     * @param preferenceInstance the jMCS Preference object in which silent report flag is stored
     * @param preferenceName the jMCS Preference key that point to the silent report flag value
     */
    public void reportToCentralRepository(final Preferences preferenceInstance, final String preferenceName) {

        // Make all the network stuff run in the background
        ThreadExecutors.getGenericExecutor().submit(new Runnable() {
            AtomicBoolean shouldPhoneHome = new AtomicBoolean(true);
            ApplicationReportingForm dialog = null;

            @Override
            public void run() {

                // If the current application does not exist in the central repository
                if (isNotKnownYet()) {

                    // If user wants to explicitly choose to report or not
                    final boolean silently = preferenceInstance.getPreferenceAsBoolean(preferenceName);
                    if (!silently) {
                        // Ask user if it is OK to phone application description back home
                        SwingUtils.invokeAndWaitEDT(new Runnable() {
                            /**
                             * Synchronized by EDT
                             */
                            @Override
                            public void run() {
                                _logger.debug("Showing report window for '{}' application", _applicationName);
                                dialog = new ApplicationReportingForm(_applicationName);
                                shouldPhoneHome.set(dialog.shouldSubmit());
                                final boolean shouldSilentlySubmit = dialog.shouldSilentlySubmit();
                                if (shouldSilentlySubmit != silently) {
                                    try {
                                        preferenceInstance.setPreference(preferenceName, shouldSilentlySubmit);
                                        preferenceInstance.saveToFile();
                                    } catch (PreferencesException ex) {
                                        _logger.warn("Could not save silent report state to preference : ", ex);
                                    }
                                }
                            }
                        });
                    } else {
                        _logger.info("Silently reporting '{}' unknown application", _applicationName);
                    }

                    // If the user agreed to report unknown app
                    if (shouldPhoneHome.get()) {
                        if (dialog == null) { // Silently
                            serializeMetaData(null, null); // Report without further data
                        } else { // Explicitly
                            final String userEmail = dialog.getUserEmail();
                            final String applicationURL = dialog.getApplicationURL();
                            serializeMetaData(userEmail, applicationURL);
                        }

                        final String xmlRepresentation = marshallApplicationDescription();
                        postXMLToRegistry(xmlRepresentation);
                    }
                }
            }
        });
    }

    /**
     * @return true if the 'name' application is unknown, false otherwise.
     */
    private boolean isNotKnownYet() {

        _logger.info("Querying JMMC SAMP application registry for '{}' application ...", _applicationName);
        boolean unknownApplicationFlag = false; // In order to skip later application reporting if registry querying goes wrong

        try {
            final String path = REGISTRY_BASE_URL + SAMP_STUB_REGISTRY_DIRECTORY + _applicationId
                    + SAMP_STUB_FILE_EXTENSION;

            final URI applicationDescriptionFileURI = Http.validateURL(path);
            final String result = Http.download(applicationDescriptionFileURI, false); // Use the multi-threaded HTTP client
            _logger.debug("HTTP response : '{}'.", result);

            // Decipher whether the meta-data is alredy registered or not
            unknownApplicationFlag = (result == null) || (result.length() == 0);
            _logger.info("SAMP application '{}' {}found in JMMC registry.", _applicationName,
                    (unknownApplicationFlag ? "not " : ""));

        } catch (IOException ioe) {
            _logger.error("Cannot get SAMP application meta-data : ", ioe);
        }

        return unknownApplicationFlag;
    }

    /**
     * Load SampStub object for the given application name.
     *
     * @param applicationName name of application
     * @return the associated samp stub
     * @throws IllegalArgumentException if applicationName is null
     * @throws IllegalStateException if io exception occurs for data retrieval
     */
    private static SampStub loadSampStubForApplication(final String applicationName) {

        if (applicationName == null) {
            throw new IllegalArgumentException("applicationName");
        }

        String applicationId = FileUtils.cleanupFileName(applicationName);
        final String path = SAMP_STUB_RESOURCE_DIRECTORY + applicationId + SAMP_STUB_FILE_EXTENSION;

        // Note : use input stream to avoid JNLP offline bug with URL (Unknown host exception)
        final URL resourceURL = ResourceUtils.getResource(path);

        try {
            return (SampStub) JAXBUtils.loadObject(resourceURL, _jaxbFactory);
        } catch (IOException ioe) {
            throw new IllegalStateException("Load failure on " + resourceURL, ioe);
        }
    }

    /**
     * @param userEmail
     * @param applicationURL
     */
    private void serializeMetaData(String userEmail, String applicationURL) {

        fr.jmmc.smprsc.data.stub.model.Metadata tmp;

        // Add user given inputs
        if ((userEmail != null) && (userEmail.length() > 0)) {
            tmp = new fr.jmmc.smprsc.data.stub.model.Metadata("email", userEmail);
            _data.getMetadatas().add(tmp);
        }
        if ((applicationURL != null) && (applicationURL.length() > 0)) {
            tmp = new fr.jmmc.smprsc.data.stub.model.Metadata(SampMetaData.HOMEPAGE_URL.id(), applicationURL);
            _data.getMetadatas().add(tmp);
        }

        // Serialize all SAMP meta data
        for (Object key : _sampMetaData.keySet()) {
            tmp = new fr.jmmc.smprsc.data.stub.model.Metadata(key.toString(), _sampMetaData.get(key).toString());
            _data.getMetadatas().add(tmp);
        }

        // Serialize all SAMP mTypes
        for (Object subscription : _sampSubscriptions.keySet()) {
            _data.getSubscriptions().add(subscription.toString());
        }
    }

    /**
     * @param xml
     */
    private void postXMLToRegistry(final String xml) {

        // Check parameter vailidty
        if (xml == null) {
            _logger.warn(
                    "Something went wrong while serializing SAMP application '{}' meta-data ... aborting report.",
                    _applicationName);
            return;
        }

        _logger.info("Sending JMMC SAMP application '{}' XML description to JMMC registry ...", _applicationName);

        try {
            final URI uri = Http.validateURL(REGISTRY_BASE_URL + REGISTRY_SUBMISSION_FORM_NAME);
            // use the multi threaded HTTP client
            final String result = Http.post(uri, false, new PostQueryProcessor() {
                /**
                 * Process the given post method to define its HTTP input fields
                 *
                 * @param method post method to complete
                 * @throws IOException if any IO error occurs
                 */
                @Override
                public void process(final PostMethod method) throws IOException {
                    method.addParameter("uid", _applicationId);
                    method.addParameter("xmlSampStub", xml);
                }
            });

            _logger.debug("HTTP response : '{}'.", result);

            // Parse result for failure
            if (result != null) {
                _logger.info("Sent SAMP application '{}' XML description to JMMC regitry.", _applicationName);
            } else {
                _logger.warn("SAMP application meta-data were not sent properly.");
            }

        } catch (IOException ioe) {
            _logger.error("Cannot send SAMP application meta-data : ", ioe);
        }
    }

    /**
     * Marshall current sampStup to a string representation.
     *
     * @return the string representation of marshalled sampStub
     * @throws XmlBindException if a JAXBException was caught while creating an marshaller
     * @throws
     */
    private String marshallApplicationDescription() throws XmlBindException {
        final StringWriter stringWriter = new StringWriter(4096); // 4K buffer

        JAXBUtils.saveObject(stringWriter, _data, _jaxbFactory);

        final String xml = stringWriter.toString();
        _logger.debug("Generated SAMP application '{}' XML description :\n{}", _applicationName, xml);

        return xml;
    }
}