/** * Helios, OpenSource Monitoring * Brought to you by the Helios Development Group * * Copyright 2007, Helios Development Group and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. * */ package org.helios.apmrouter.subscription; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; import javax.management.Notification; import org.helios.apmrouter.dataservice.json.JsonRequest; import org.helios.apmrouter.server.ServerComponentBean; import org.helios.apmrouter.subscription.criteria.SubscriptionCriteria; import org.helios.apmrouter.subscription.criteria.SubscriptionCriteriaInstance; import org.helios.apmrouter.subscription.criteria.builder.SubscriptionCriteriaBuilder; import org.helios.apmrouter.subscription.criteria.builder.SubscriptionCriteriaBuilderStartedEvent; import org.helios.apmrouter.subscription.criteria.builder.SubscriptionCriteriaBuilderStoppedEvent; import org.helios.apmrouter.subscription.session.DefaultSubscriptionSessionImpl; import org.helios.apmrouter.subscription.session.NettySubscriberChannel; import org.helios.apmrouter.subscription.session.SubscriptionSession; import org.helios.apmrouter.util.SystemClock; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.channel.ChannelLocal; import org.springframework.context.ApplicationEvent; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.jmx.export.annotation.ManagedMetric; import org.springframework.jmx.export.annotation.ManagedNotification; import org.springframework.jmx.export.annotation.ManagedNotifications; import org.springframework.jmx.support.MetricType; /** * <p>Title: SubscriptionService</p> * <p>Description: Factory and lifecycle manager for event subscriptions</p> * <p>Summary:<p> * <p>Moving parts:<ul> * <li><b><code>SubscriptionCriteria</code></b>: Defines the source and filtering of events delivered to a <b><code>Subscriber</code></b></li> * <li><b><code>SubscriptionSource</code></b>: A source of subscribable events</li> * <li><b><code>Subscriber</code></b>: A recipient of events from a <b><code>SubscriptionSource</code></b> and filtered by a <b><code>SubscriptionCriteria</code></b></li> * <li><b><code>Subscription</code></b>: The unique combination of a <b><code>Subscriber</code></b>, a <b><code>SubscriptionCriteria</code></b> and a <b><code>SubscriptionSource</code></b>.</li> * <li><b><code>SubscriberSession</code></b>: A notional session associated to a <b><code>Subscriber</code></b> that can be terminated and will trigger the termination of the <b><code>Subscriber</code></b>'s <b><code>Subscription</code></b>s</li> * </ul></p> * <p>Company: Helios Development Group LLC</p> * @author Whitehead (nwhitehead AT heliosdev DOT org) * <p><code>org.helios.apmrouter.subscription.SubscriptionService</code></p> */ @ManagedNotifications({ @ManagedNotification(notificationTypes={SubscriptionService.NOTIF_SUB_STARTED}, name="javax.management.Notification", description="Notification issued when a subscription session starts a new subscription"), @ManagedNotification(notificationTypes={SubscriptionService.NOTIF_SUB_STOPPED}, name="javax.management.Notification", description="Notification issued when a subscription session stops a subscription") }) public class SubscriptionService extends ServerComponentBean { /** A channel local of subscription sessions */ protected final ChannelLocal<SubscriptionSession> subSessions = new ChannelLocal<SubscriptionSession>(true); /** A map of available {@link SubscriptionCriteriaBuilder} instances keyed by their bean name */ protected final Map<String, SubscriptionCriteriaBuilder<?,?,?>> builders = new ConcurrentHashMap<String, SubscriptionCriteriaBuilder<?,?,?>>(); /** Serial number generator for jmx notifications */ protected final AtomicLong jmxNotifSerial = new AtomicLong(0L); /** Notification type for a subscription started event */ public static final String NOTIF_SUB_STARTED = "subscription.started"; /** Notification type for a subscription stopped event */ public static final String NOTIF_SUB_STOPPED = "subscription.stopped"; /** * Sends a subscription started notification * @param criteria The criteria for which the subscription was started */ public void sendSubStarted(SubscriptionCriteriaInstance<?> criteria) { Notification notif = new Notification(NOTIF_SUB_STARTED, criteria.getSubscriptionCriteria().getEventFilter(), jmxNotifSerial.incrementAndGet(), SystemClock.time(), "Subscription Started [" + criteria + "]"); notif.setUserData(criteria); sendNotification(notif); } /** * Sends a subscription stopped notification * @param criteria The criteria for which the subscription was stopped */ public void sendSubStopped(SubscriptionCriteriaInstance<?> criteria) { Notification notif = new Notification(NOTIF_SUB_STOPPED, criteria.getSubscriptionCriteria().getEventFilter(), jmxNotifSerial.incrementAndGet(), SystemClock.time(), "Subscription Stopped [" + criteria + "]"); notif.setUserData(criteria); sendNotification(notif); } /** * <p>Responds <code>true</code> for {@link SubscriptionCriteriaBuilderStartedEvent}s or {@link SubscriptionCriteriaBuilderStoppedEvent}s. * {@inheritDoc} * @see org.helios.apmrouter.server.ServerComponentBean#supportsEventType(java.lang.Class) */ @Override public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) { return (SubscriptionCriteriaBuilderStartedEvent.class.isAssignableFrom(eventType)||SubscriptionCriteriaBuilderStoppedEvent.class.isAssignableFrom(eventType)); } /** * Returns the number of active subscriber sessions * @return the number of active subscriber sessions */ @ManagedMetric(category="SubscriptionService", metricType=MetricType.COUNTER, description="The number of active subscriber sessions") public long getActiveSubscriberSessions() { return getMetricValue("ActiveSubscriberSessions"); } /** * {@inheritDoc} * @see org.helios.apmrouter.server.ServerComponent#getSupportedMetricNames() */ @Override public Set<String> getSupportedMetricNames() { Set<String> _metrics = new HashSet<String>(super.getSupportedMetricNames()); _metrics.add("ActiveSubscriberSessions"); return _metrics; } /** * <p>Responds <code>true</code>. * {@inheritDoc} * @see org.helios.apmrouter.server.ServerComponentBean#supportsSourceType(java.lang.Class) */ @Override public boolean supportsSourceType(Class<?> sourceType) { return true; } /** * Called when a new {@link SubscriptionCriteriaBuilder} starts. * @param event The start event */ public void onApplicationEvent(SubscriptionCriteriaBuilderStartedEvent event) { builders.put(event.getBeanName(), event.getSource()); } /** * Called when a new {@link SubscriptionCriteriaBuilder} stops. * @param event The stop event */ public void onApplicationEvent(SubscriptionCriteriaBuilderStoppedEvent event) { builders.remove(event.getBeanName()); } /** * On start, searches the app context for {@link SubscriptionCriteriaBuilder}s not already registered. * @param event The app context refresh event */ @SuppressWarnings("rawtypes") @Override public void onApplicationContextRefresh(ContextRefreshedEvent event) { Map<String, SubscriptionCriteriaBuilder> inits = event.getApplicationContext().getBeansOfType(SubscriptionCriteriaBuilder.class); if(!inits.isEmpty()) { for(Map.Entry<String, SubscriptionCriteriaBuilder> entry: inits.entrySet()) { if(!builders.containsKey(entry.getKey())) { builders.put(entry.getKey(), entry.getValue()); info("Adding Discovered SubscriptionCriteriaBuilder [", entry.getKey(), "]" ); } } } } /** * Starts or returns the ID of the subscription session for the passed channel * @param channel The channel to retrieve the session for * @return the unique ID of the subscription session */ public long startSubscriptionSession(Channel channel) { if(channel==null) throw new IllegalArgumentException("The passed channel was null", new Throwable()); if(!channel.isOpen()) throw new IllegalStateException("The passed channel is not open [" + channel + "]", new Throwable()); info("Inquiring about SubscriptionSession for channel [", channel, "]"); SubscriptionSession session = subSessions.get(channel); if(session==null) { synchronized(this) { session = subSessions.get(channel); if(session==null) { session = new DefaultSubscriptionSessionImpl(this, new NettySubscriberChannel(channel)); subSessions.set(channel, session); incr("ActiveSubscriberSessions"); } } } return session.getSubscriptionSessionId(); } /** * Callback from sessions when they terminate * @param session the terminated session */ public void onTerminate(SubscriptionSession session) { decr("ActiveSubscriberSessions"); info("Terminated Subscription Session [", session.getSubscriptionSessionId(), "]"); } /** * Returns the subscription session for the passed channel * @param channel The channel to get the subscription session for * @return The channel's subscription session or null if it does not have one */ public SubscriptionSession getSubscriptionSession(Channel channel) { return subSessions.get(channel); } /** * Returns the session Id of the passed channel's subscription session, creating a session if one does not exist. * @param channel The channel to get the session Id for. * @return the session Id for the passed channel */ public long getSessionId(Channel channel) { return startSubscriptionSession(channel); } public void getSubSessions() { //return subSessions.iterator() } /** * Returns the subscription session Id for the passed channel, or null if a session does not exist. * @param channel The channel to check for * @return the session Id if one exists, null otherwise. */ public Long getSessionIdOrNull(Channel channel) { if(channel==null) throw new IllegalArgumentException("The passed channel was null", new Throwable()); SubscriptionSession session = subSessions.get(channel); return session==null ? null : session.getSubscriptionSessionId(); } /** * Determines if the passed channel has a current subscription session * @param channel The channel to check for a current subscription session * @return true if the passed channel has a current subscription session, false otherwise */ public boolean hasSession(Channel channel) { if(channel==null) throw new IllegalArgumentException("The passed channel was null", new Throwable()); return subSessions.get(channel)!=null; } /** * Adds a subscription definition to the subscription session for the passed channel. * If a session does not already exist, it will be created, but an additional call to {@link SubscriptionService#getSessionId(Channel)} may be required to get the session Id. * @param channel The channel to add criteria for * @param criteria The criteria to add * @param request The original json request * @return the Id of the criteria subscription */ public long addCriteria(Channel channel, SubscriptionCriteria<?,?,?> criteria, JsonRequest request) { if(channel==null) throw new IllegalArgumentException("The passed channel was null", new Throwable()); startSubscriptionSession(channel); SubscriptionSession session = subSessions.get(channel); info("Adding Criteria for channel [", channel.getId(), "]\n", criteria); return session.addCriteria(criteria, session, request); } /** * Returns the names of the available builders * @return the names of the available builders */ @ManagedAttribute(description="The names of the available builders") public String[] getAvailableBuilders() { return new HashSet<String>(builders.keySet()).toArray(new String[builders.size()]); } /** * Returns the named builder * @param builderName The name of the builder * @return the named builder of null if one was not found */ public SubscriptionCriteriaBuilder<?,?,?> getBuilder(String builderName) { return builders.get(builderName); } /** * Sends a notification * @param notification The notification to send * @see javax.management.NotificationBroadcasterSupport#sendNotification(javax.management.Notification) */ public void sendNotification(Notification notification) { notificationPublisher.sendNotification(notification); } }