Java tutorial
/* * RDV * Real-time Data Viewer * http://rdv.googlecode.com/ * * Copyright (c) 2005-2007 University at Buffalo * Copyright (c) 2005-2007 NEES Cyberinfrastructure Center * Copyright (c) 2008 Palta Software * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * $URL$ * $Revision$ * $Date$ * $Author$ */ package org.rdv.rbnb; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.rdv.DataViewer; import org.rdv.data.Channel; import org.rdv.data.LocalChannel; import org.rdv.data.LocalChannelManager; import com.rbnb.sapi.ChannelMap; import com.rbnb.sapi.ChannelTree; import com.rbnb.sapi.SAPIException; import com.rbnb.sapi.Sink; import com.rbnb.sapi.ChannelTree.NodeTypeEnum; /** * A class to fetch metadata from the server and post it to listeners. Methods * are also included to access metadata for individual channels. * * @author Jason P. Hanley */ public class MetadataManager { static Log log = LogFactory.getLog(MetadataManager.class.getName()); private RBNBController rbnbController; /** * The name of the RBNB sink used to get metadata */ private String rbnbSinkName = "RDVMetadata"; /** * Listeners for metadata updates. */ private ArrayList<MetadataListener> metadataListeners; /** * Listeners for markers. */ private ArrayList<DataListener> markerListeners; /** * Map of channel objects created from metadata. */ private Map<String, Channel> channels; /** * Metadata channel tree. */ private ChannelTree ctree; /** * Whether the metadata update thread is running. */ private boolean update; /** * Whether the update thread is sleeping. */ private Boolean sleeping; /** * The thread updating the metadata periodically. */ private Thread updateThread; /** * The time to sleep between each metadata update. */ private static final long updateRate = 10000; /** * The timeout used when fetching data. */ private static final long FETCH_TIMEOUT = 15000; /** * The maximum depth to make recursive requests with (to detect circular * references). */ private static final int MAX_REQUEST_DEPTH = 5; /** * Create the class using the RBNBController for connection information. * * @param rbnbController the RBNBController to use */ public MetadataManager(RBNBController rbnbController) { this.rbnbController = rbnbController; try { InetAddress addr = InetAddress.getLocalHost(); String hostname = addr.getHostName(); rbnbSinkName += "@" + hostname; } catch (UnknownHostException e) { } metadataListeners = new ArrayList<MetadataListener>(); channels = new HashMap<String, Channel>(); ctree = null; update = false; sleeping = false; updateThread = null; markerListeners = new ArrayList<DataListener>(); } /** * Triggers a metadata update. This will return immediately and the metadata * will be posted to the listeners when available. */ public void updateMetadataBackground() { if (update) { synchronized (sleeping) { if (sleeping) { updateThread.interrupt(); } } } } /** * Start the thread that periodically updates the metadata. */ public void startUpdating() { if (update == true) { return; } final Sink metadataSink = new Sink(); try { metadataSink.OpenRBNBConnection(rbnbController.getRBNBConnectionString(), rbnbSinkName); } catch (SAPIException e) { log.error("Failed to connect to RBNB server: " + e.getMessage()); e.printStackTrace(); return; } updateMetadata(metadataSink); updateThread = new Thread("MetadataManager") { public void run() { log.info("RBNB Metadata thread is starting."); updateMetadataThread(metadataSink); metadataSink.CloseRBNBConnection(); channels.clear(); ctree = null; log.info("RBNB Metadata thread is stopping."); } }; update = true; updateThread.start(); } /** * Stops the metadata update thread. */ public void stopUpdating() { update = false; if (updateThread != null && sleeping) { updateThread.interrupt(); } } /** * Updates the metadata and sleeps <code>updateRate</code>. The * <code>Sink</code> must be opened and will not be closed when this method * exits. * * @param metadataSink The sink connection to the RBNB server * @see #updateMetadata(Sink) */ private void updateMetadataThread(Sink metadataSink) { while (update) { synchronized (sleeping) { sleeping = true; try { Thread.sleep(updateRate); } catch (InterruptedException e) { } sleeping = false; } if (update) { updateMetadata(metadataSink); } } fireMetadataUpdated(null); fireMarkersUpdated(null); } /** * Updates the metadata and posts it to listeners. It also notifies all * threads waiting on this object. * * @param metadataSink the RBNB sink to use for the server connection */ private synchronized void updateMetadata(Sink metadataSink) { log.info("Updating channel listing at " + DataViewer.formatDate(System.currentTimeMillis())); Map<String, Channel> newChannels = new HashMap<String, Channel>(); ChannelTree channelTree; try { //create metadata channel tree channelTree = getChannelTree(metadataSink, newChannels); } catch (SAPIException e) { log.error("Failed to update metadata: " + e.getMessage() + "."); if (!metadataSink.VerifyConnection()) { log.error("Metadata RBNB connection is severed, try to reconnect to " + rbnbController.getRBNBConnectionString() + "."); metadataSink.CloseRBNBConnection(); try { metadataSink.OpenRBNBConnection(rbnbController.getRBNBConnectionString(), "RDVMetadata"); } catch (SAPIException error) { log.error("Failed to connect to RBNB server: " + error.getMessage()); error.printStackTrace(); } } return; } if (LocalChannelManager.getInstance().hasChannels()) { ChannelTree localChannelTree = getLocalChannelTree(newChannels, channelTree); ctree = localChannelTree.merge(channelTree); } else { ctree = channelTree; } channels = newChannels; //notify metadata listeners fireMetadataUpdated(ctree); } /** * Get the metadata channel tree for the whole server. This will populate the * channel map with channel objects derived from the metadata. * * @param sink the sink connection to the RBNB server * @param channels the map to populate with channel objects * @return the metadata channel tree * @throws SAPIException if a server error occurs */ private ChannelTree getChannelTree(Sink sink, Map<String, Channel> channels) throws SAPIException { return getChannelTree(sink, null, channels, 0); } /** * Get the metadata channel tree for the given <code>path</code>. This will * populate the channel map with channel objects derived from the metadata. * This will recursively make requests for child servers and plugins up to the * maximum request depth of {@value #MAX_REQUEST_DEPTH}. * * @param sink sink the sink connection to the RBNB server * @param path the path for the desired metadata * @param channels the map to populate with channel objects * @param depth the depth of the request * @return the metadata channel tree for the given path * @throws SAPIException if a server error occurs * @see #MAX_REQUEST_DEPTH */ private ChannelTree getChannelTree(Sink sink, String path, Map<String, Channel> channels, int depth) throws SAPIException { depth++; ChannelTree ctree = ChannelTree.EMPTY_TREE; ChannelMap markerChannelMap = new ChannelMap(); ChannelMap cmap = new ChannelMap(); if (path == null) { path = ""; cmap.Add("..."); } else { cmap.Add(path + "/..."); } sink.RequestRegistration(cmap); cmap = sink.Fetch(FETCH_TIMEOUT, cmap); if (cmap.GetIfFetchTimedOut()) { log.error("Failed to get metadata. Fetch timed out."); return ctree; } ctree = ChannelTree.createFromChannelMap(cmap); //store user metadata in channel objects String[] channelList = cmap.GetChannelList(); for (int i = 0; i < channelList.length; i++) { int channelIndex = cmap.GetIndex(channelList[i]); if (channelIndex != -1) { ChannelTree.Node node = ctree.findNode(channelList[i]); String userMetadata = cmap.GetUserInfo(channelIndex); Channel channel = new RBNBChannel(node, userMetadata); channels.put(channelList[i], channel); //look for marker channels String mimeType = channel.getMetadata("mime"); if (mimeType != null && mimeType.compareToIgnoreCase(EventMarker.MIME_TYPE) == 0) { markerChannelMap.Add(node.getFullName()); } } } Iterator<?> it = ctree.iterator(); while (it.hasNext()) { ChannelTree.Node node = (ChannelTree.Node) it.next(); NodeTypeEnum type = node.getType(); // look for child servers or plugins and get their channels if ((type == ChannelTree.SERVER || type == ChannelTree.PLUGIN) && !path.startsWith(node.getFullName()) && depth < MAX_REQUEST_DEPTH) { ChannelTree childChannelTree = getChannelTree(sink, node.getFullName(), channels, depth); ctree = childChannelTree.merge(ctree); } } if (markerChannelMap.NumberOfChannels() > 0) { double markersDuration = System.currentTimeMillis() / 1000d; //request from start of marker channels to now sink.Request(markerChannelMap, 0, markersDuration, "absolute"); markerChannelMap = sink.Fetch(FETCH_TIMEOUT, markerChannelMap); if (!markerChannelMap.GetIfFetchTimedOut()) { //notify marker listeners fireMarkersUpdated(markerChannelMap); } else { log.error("Failed to get event markers. Fetched timed out."); } } return ctree; } /** * Gets the metadata channel tree for local channels. This will add a * <code>LocalChannel</code> to <code>channels</code> for each local channel. * * @param channels the map of channel names to channel objects * @param serverChannelTree the metadata channel tree of server channels * @return the metadata channel tree of local channels * @see LocalChannelManager#updateMetadata(LocalChannelMap, ChannelTree) */ private ChannelTree getLocalChannelTree(Map<String, Channel> channels, ChannelTree serverChannelTree) { LocalChannelManager localChannelManager = LocalChannelManager.getInstance(); // get channel tree for local channels ChannelTree channelTree = localChannelManager.getMetadata(serverChannelTree); // add local channels to channels map List<LocalChannel> localChannels = localChannelManager.getChannels(); for (LocalChannel localChannel : localChannels) { channels.put(localChannel.getName(), localChannel); } return channelTree; } /** * Return the latest metadata channel tree. * * @return the metadata channel tree. */ public ChannelTree getChannelTree() { return ctree; } /** * Return a channel object for the given <code>channelName</code>. * * @param channelName the desired channel * @return the channel object for the channel name, or null if the channel is * not found */ public Channel getChannel(String channelName) { return channels.get(channelName); } /** * Returns a list of channel objects. * * @param channelNames the list of channels names to get * @return a list of channels */ public List<Channel> getChannels(List<String> channelNames) { List<Channel> channelsRequest = new ArrayList<Channel>(); for (String channelName : channelNames) { Channel channel = getChannel(channelName); if (channel != null) { channelsRequest.add(channel); } } return channelsRequest; } /** * Gets a list of channels. * * @return a list of channels */ public List<Channel> getChannels() { List<Channel> allChannels = new ArrayList<Channel>(); for (String channel : channels.keySet()) { allChannels.add(channels.get(channel)); } return allChannels; } /** * Add a listener for metadata updates. * * @param listener the metadata listener */ public void addMetadataListener(MetadataListener listener) { metadataListeners.add(listener); } /** * Add a listener for marker data. * * @param listener the marker listener to add */ public void addMarkerListener(DataListener listener) { markerListeners.add(listener); } /** * Remove a listener for metadata updates. * * @param listener the metadata listener */ public void removeMetadataListener(MetadataListener listener) { metadataListeners.remove(listener); } /** * Remove a listener for marker data. * * @param listener the marker listener to remove */ public void removeMarkerListener(DataListener listener) { markerListeners.remove(listener); } /** * Post metadata updates to the subscribed listeners. * * @param channelTree the new metadata channel tree */ private void fireMetadataUpdated(ChannelTree channelTree) { for (MetadataListener listener : metadataListeners) { listener.channelTreeUpdated(channelTree); } } /** * Post marker data to subscribed listeners. * * @param cmap the channel map containing the marker data */ protected void fireMarkersUpdated(ChannelMap cmap) { for (DataListener listener : markerListeners) { listener.postData(cmap); } } }