com.rometools.certiorem.hub.Hub Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rome-certiorem Show documentation
Show all versions of rome-certiorem Show documentation
A PubSubHubub implementation for Java based on ROME
/**
*
* 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 com.rometools.certiorem.hub;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.rometools.certiorem.HttpStatusCodeException;
import com.rometools.certiorem.hub.Notifier.SubscriptionSummaryCallback;
import com.rometools.certiorem.hub.Verifier.VerificationCallback;
import com.rometools.certiorem.hub.data.HubDAO;
import com.rometools.certiorem.hub.data.Subscriber;
import com.rometools.certiorem.hub.data.SubscriptionSummary;
import com.rometools.fetcher.FeedFetcher;
import com.rometools.rome.feed.synd.SyndFeed;
/**
* 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 Logger LOG = LoggerFactory.getLogger(Hub.class);
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;
validSchemes = STANDARD_SCHEMES;
validPorts = Collections.emptySet();
validTopics = Collections.emptySet();
}
/**
* 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;
final Set readOnlySchemes = Collections.unmodifiableSet(validSchemes);
this.validSchemes = readOnlySchemes == null ? STANDARD_SCHEMES : readOnlySchemes;
final Set readOnlyPorts = Collections.unmodifiableSet(validPorts);
if (readOnlyPorts == null) {
this.validPorts = Collections.emptySet();
} else {
this.validPorts = readOnlyPorts;
}
final Set readOnlyTopics = Collections.unmodifiableSet(validTopics);
if (validTopics == null) {
this.validTopics = Collections.emptySet();
} else {
this.validTopics = 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(final String requestHost, final String topic) {
// FIXME assert should not be used for validation because it can be disabled
assert validTopics.isEmpty() || validTopics.contains(topic) : "That topic is not supported by this hub. " + topic;
LOG.debug("Sending notification for {}", topic);
try {
final List extends Subscriber> subscribers = dao.subscribersForTopic(topic);
if (subscribers.isEmpty()) {
LOG.debug("No subscribers to notify for {}", topic);
return;
}
final List extends SubscriptionSummary> summaries = dao.summariesForTopic(topic);
int total = 0;
final StringBuilder hosts = new StringBuilder();
for (final SubscriptionSummary s : summaries) {
if (s.getSubscribers() > 0) {
total += s.getSubscribers();
hosts.append(" (").append(s.getHost()).append("; ").append(s.getSubscribers()).append(" subscribers)");
}
}
final StringBuilder userAgent = new StringBuilder("ROME-Certiorem (+http://").append(requestHost).append("; ").append(total)
.append(" subscribers)").append(hosts);
final SyndFeed feed = fetcher.retrieveFeed(userAgent.toString(), new URL(topic));
LOG.debug("Got feed for {} Sending to {} subscribers.", topic, subscribers.size());
notifier.notifySubscribers(subscribers, feed, new SubscriptionSummaryCallback() {
@Override
public void onSummaryInfo(final SubscriptionSummary summary) {
dao.handleSummary(topic, summary);
}
});
} catch (final Exception ex) {
LOG.debug("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(final String callback, final String topic, final String verify, final long lease_seconds, final String secret,
final String verify_token) {
LOG.debug("{} wants to subscribe to {}", callback, topic);
try {
try {
// FIXME assert should not be used for validation because it can be disabled
assert callback != null : "Callback URL is required.";
assert topic != null : "Topic URL is required.";
final URI uri = new URI(callback);
assert validSchemes.contains(uri.getScheme()) : "Not a valid protocol " + uri.getScheme();
assert validPorts.isEmpty() || validPorts.contains(uri.getPort()) : "Not a valid port " + uri.getPort();
assert validTopics.isEmpty() || validTopics.contains(topic) : "Not a supported topic " + topic;
} catch (final 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);
final VerificationCallback verified = new VerificationCallback() {
@Override
public void onVerify(final boolean verified) {
if (verified) {
LOG.debug("Verified {} subscribed to {}", subscriber.getCallback(), subscriber.getTopic());
dao.addSubscriber(subscriber);
}
}
};
if (Subscriber.VERIFY_SYNC.equals(subscriber.getVerify())) {
final boolean result = verifier.verifySubcribeSyncronously(subscriber);
verified.onVerify(result);
return result;
} else {
verifier.verifySubscribeAsyncronously(subscriber, verified);
return null;
}
} catch (final AssertionError ae) {
throw new HttpStatusCodeException(400, ae.getMessage(), ae);
} catch (final Exception e) {
throw new HttpStatusCodeException(500, e.getMessage(), e);
}
}
public Boolean unsubscribe(final String callback, final String topic, final String verify, final String secret, final 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)) {
final boolean ret = verifier.verifyUnsubcribeSyncronously(subscriber);
if (ret) {
dao.removeSubscriber(topic, callback);
}
} else {
verifier.verifyUnsubscribeAsyncronously(subscriber, new VerificationCallback() {
@Override
public void onVerify(final boolean verified) {
LOG.debug("Unsubscribe for {} at {} verified {}", subscriber.getTopic(), subscriber.getCallback(), verified);
if (verified) {
dao.removeSubscriber(topic, callback);
}
}
});
}
return null;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy