All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.rometools.certiorem.hub.Hub Maven / Gradle / Ivy

/**
 *
 *  Copyright (C) The ROME Team  2011
 *
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.rometools.certiorem.hub;

import com.sun.syndication.feed.synd.SyndFeed;

import org.rometools.certiorem.HttpStatusCodeException;
import org.rometools.certiorem.hub.Notifier.SubscriptionSummaryCallback;
import org.rometools.certiorem.hub.Verifier.VerificationCallback;
import org.rometools.certiorem.hub.data.HubDAO;
import org.rometools.certiorem.hub.data.Subscriber;
import org.rometools.certiorem.hub.data.SubscriptionSummary;

import org.rometools.fetcher.FeedFetcher;

import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
 * The basic business logic controller for the Hub implementation. It is intended
 * to be usable under a very thin servlet wrapper, or other, non-HTTP notification
 * methods you might want to use.
 *
 * @author robert.cooper
 */
public class Hub {
    private static final HashSet STANDARD_SCHEMES = new HashSet();

    static {
        STANDARD_SCHEMES.add("http");
        STANDARD_SCHEMES.add("https");
    }

    private final FeedFetcher fetcher;
    private final HubDAO dao;
    private final Notifier notifier;
    private final Set validPorts;
    private final Set validSchemes;
    private final Set validTopics;
    private final Verifier verifier;

    /**
     * Constructs a new Hub instance
     * @param dao The persistence HubDAO to use
     * @param verifier The verification strategy to use.
     */
    public Hub(final HubDAO dao, final Verifier verifier, final Notifier notifier, final FeedFetcher fetcher) {
        this.dao = dao;
        this.verifier = verifier;
        this.notifier = notifier;
        this.fetcher = fetcher;
        this.validSchemes = STANDARD_SCHEMES;
        this.validPorts = Collections.EMPTY_SET;
        this.validTopics = Collections.EMPTY_SET;
    }

    /**
     * Constructs a new Hub instance.
     * @param dao The persistence HubDAO to use
     * @param verifier The verification strategy to use
     * @param validSchemes A list of valid URI schemes for callbacks (default: http, https)
     * @param validPorts A list of valid port numbers for callbacks (default: any)
     * @param validTopics A set of valid topic URIs which can be subscribed to (default: any)
     */
    public Hub(final HubDAO dao, final Verifier verifier, final Notifier notifier, final FeedFetcher fetcher,
        final Set validSchemes, final Set validPorts, final Set validTopics) {
        this.dao = dao;
        this.verifier = verifier;
        this.notifier = notifier;
        this.fetcher = fetcher;

        Set readOnlySchemes = Collections.unmodifiableSet(validSchemes);
        this.validSchemes = (readOnlySchemes == null) ? STANDARD_SCHEMES : readOnlySchemes;

        Set readOnlyPorts = Collections.unmodifiableSet(validPorts);
        this.validPorts = (readOnlyPorts == null) ? Collections.EMPTY_SET : readOnlyPorts;

        Set readOnlyTopics = Collections.unmodifiableSet(validTopics);
        this.validTopics = (readOnlyTopics == null) ? Collections.EMPTY_SET : readOnlyTopics;
    }

    /**
     * Sends a notification to the subscribers
     * @param requestHost the host name the hub is running on. (Used for the user agent)
     * @param topic the URL of the topic that was updated.
     * @throws HttpStatusCodeException a wrapper exception with a recommended status code for the request.
     */
    public void sendNotification(String requestHost, final String topic) {
        assert this.validTopics.isEmpty() || this.validTopics.contains(topic) : "That topic is not supported by this hub. " +
        topic;
        Logger.getLogger(Hub.class.getName()).log(Level.FINE, "Sending notification for {0}", topic);
        try {
            List subscribers = dao.subscribersForTopic(topic);

            if (subscribers.isEmpty()) {
                Logger.getLogger(Hub.class.getName()).log(Level.FINE, "No subscribers to notify for {0}", topic);
                return;
            }

            List summaries = (List) dao.summariesForTopic(topic);
            int total = 0;
            StringBuilder hosts = new StringBuilder();

            for (SubscriptionSummary s : summaries) {
                if (s.getSubscribers() > 0) {
                    total += s.getSubscribers();
                    hosts.append(" (")
                         .append(s.getHost())
                         .append("; ")
                         .append(s.getSubscribers())
                         .append(" subscribers)");
                }
            }

            StringBuilder userAgent = new StringBuilder("ROME-Certiorem (+http://").append(requestHost)
                                                                                   .append("; ")
                                                                                   .append(total)
                                                                                   .append(" subscribers)")
                                                                                   .append(hosts);
            SyndFeed feed = fetcher.retrieveFeed(userAgent.toString(), new URL(topic));
            Logger.getLogger(Hub.class.getName()).log(Level.FINE, "Got feed for {0}  Sending to {1} subscribers.", new Object[]{topic, subscribers.size()});
            this.notifier.notifySubscribers((List) subscribers, feed,
                new SubscriptionSummaryCallback() {
                    @Override
                    public void onSummaryInfo(SubscriptionSummary summary) {
                        dao.handleSummary(topic, summary);
                    }
                });
        } catch (Exception ex) {
            Logger.getLogger(Hub.class.getName())
                  .log(Level.SEVERE, "Exception getting " + topic, ex);
            throw new HttpStatusCodeException(500, ex.getMessage(), ex);
        }
    }

    /**
     * Subscribes to a topic.
     * @param callback Callback URI
     * @param topic Topic URI
     * @param verify Verification Type
     * @param lease_seconds Duration of the lease
     * @param secret Secret value
     * @param verify_token verify_token;
     * @return Boolean.TRUE if the subscription succeeded synchronously,
     *  Boolean.FALSE if the subscription failed synchronously, or null if the request is asynchronous.
     * @throws HttpStatusCodeException a wrapper exception with a recommended status code for the request.
     */
    public Boolean subscribe(String callback, String topic, String verify, long lease_seconds, String secret,
        String verify_token) {
        Logger.getLogger(Hub.class.getName()).log(Level.FINE, "{0} wants to subscribe to {1}", new Object[]{callback, topic});
        try {
            try {
                assert callback != null : "Callback URL is required.";
                assert topic != null : "Topic URL is required.";

                URI uri = new URI(callback);
                assert this.validSchemes.contains(uri.getScheme()) : "Not a valid protocol " + uri.getScheme();
                assert this.validPorts.isEmpty() || this.validPorts.contains(uri.getPort()) : "Not a valid port " +
                uri.getPort();
                assert this.validTopics.isEmpty() || this.validTopics.contains(topic) : "Not a supported topic " +
                topic;
            } catch (URISyntaxException ex) {
                assert false : "Not a valid URI " + callback;
            }
            assert (verify != null) &&
            (verify.equals(Subscriber.VERIFY_ASYNC) || verify.equals(Subscriber.VERIFY_SYNC)) : "Unexpected verify value " +
            verify;

            final Subscriber subscriber = new Subscriber();
            subscriber.setCallback(callback);
            subscriber.setLeaseSeconds(lease_seconds);
            subscriber.setSecret(secret);
            subscriber.setTopic(topic);
            subscriber.setVerify(verify);
            subscriber.setVertifyToken(verify_token);

            VerificationCallback verified = new VerificationCallback() {
                    @Override
                    public void onVerify(boolean verified) {
                        if (verified) {
                            Logger.getLogger(Hub.class.getName()).log(Level.FINE, "Verified {0} subscribed to {1}", new Object[]{subscriber.getCallback(), subscriber.getTopic()});
                            dao.addSubscriber(subscriber);
                        }
                    }
                };

            if (Subscriber.VERIFY_SYNC.equals(subscriber.getVerify())) {
                boolean result = verifier.verifySubcribeSyncronously(subscriber);
                verified.onVerify(result);

                return result;
            } else {
                verifier.verifySubscribeAsyncronously(subscriber, verified);

                return null;
            }
        } catch (AssertionError ae) {
            throw new HttpStatusCodeException(400, ae.getMessage(), ae);
        } catch (Exception e) {
            throw new HttpStatusCodeException(500, e.getMessage(), e);
        }
    }

    public Boolean unsubscribe(final String callback, final String topic, String verify, String secret, String verifyToken) {
        final Subscriber subscriber = dao.findSubscriber(topic, callback);
        if(subscriber == null){
            throw new HttpStatusCodeException(400, "Not a valid subscription.", null);
        }
        subscriber.setVertifyToken(verifyToken);
        subscriber.setSecret(secret);
        if(Subscriber.VERIFY_SYNC.equals(verify)){

            boolean ret = verifier.verifyUnsubcribeSyncronously(subscriber);
            if(ret){
                dao.removeSubscriber(topic, callback);
            }
        } else {
            verifier.verifyUnsubscribeAsyncronously(subscriber, new VerificationCallback(){

                @Override
                public void onVerify(boolean verified) {
                    Logger.getLogger(Hub.class.getName()).log(Level.FINE, "Unsubscribe for {0} at {1} verified {2}", new Object[]{subscriber.getTopic(), subscriber.getCallback(), verified});
                    if(verified){
                        dao.removeSubscriber(topic, callback);
                    }
                }

            });
        }
        return null;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy