/******************************************************************************* * Copyright (c) 2011 The Board of Trustees of the Leland Stanford Junior University * as Operator of the SLAC National Accelerator Laboratory. * Copyright (c) 2011 Brookhaven National Laboratory. * EPICS archiver appliance is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. *******************************************************************************/ package org.epics.archiverappliance.config; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.management.ManagementFactory; import java.lang.management.PlatformLoggingMXBean; import java.net.InetAddress; import java.net.URL; import java.net.UnknownHostException; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Paths; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; import javax.servlet.ServletContext; import org.apache.commons.validator.routines.InetAddressValidator; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.epics.archiverappliance.StoragePlugin; import org.epics.archiverappliance.common.ProcessMetrics; import org.epics.archiverappliance.common.TimeUtils; import org.epics.archiverappliance.config.PVTypeInfoEvent.ChangeType; import org.epics.archiverappliance.config.exception.AlreadyRegisteredException; import org.epics.archiverappliance.config.exception.ConfigException; import org.epics.archiverappliance.config.persistence.MySQLPersistence; import org.epics.archiverappliance.config.pubsub.PubSubEvent; import org.epics.archiverappliance.engine.ArchiveEngine; import org.epics.archiverappliance.engine.pv.EngineContext; import org.epics.archiverappliance.etl.common.PBThreeTierETLPVLookup; import org.epics.archiverappliance.mgmt.MgmtPostStartup; import org.epics.archiverappliance.mgmt.MgmtRuntimeState; import org.epics.archiverappliance.mgmt.NonMgmtPostStartup; import org.epics.archiverappliance.mgmt.bpl.cahdlers.NamesHandler; import org.epics.archiverappliance.mgmt.policy.ExecutePolicy; import org.epics.archiverappliance.mgmt.policy.PolicyConfig; import org.epics.archiverappliance.mgmt.policy.PolicyConfig.SamplingMethod; import org.epics.archiverappliance.retrieval.RetrievalState; import org.epics.archiverappliance.retrieval.channelarchiver.XMLRPCClient; import org.epics.archiverappliance.utils.ui.GetUrlContent; import org.epics.archiverappliance.utils.ui.JSONDecoder; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.xml.sax.SAXException; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.cache.RemovalListener; import com.google.common.cache.RemovalNotification; import com.google.common.eventbus.AsyncEventBus; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; import com.hazelcast.client.HazelcastClient; import com.hazelcast.client.config.ClientConfig; import com.hazelcast.config.Config; import com.hazelcast.config.XmlConfigBuilder; import com.hazelcast.core.Cluster; import com.hazelcast.core.EntryEvent; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.core.IMap; import com.hazelcast.core.ITopic; import com.hazelcast.core.Member; import com.hazelcast.core.MemberAttributeEvent; import com.hazelcast.core.MembershipEvent; import com.hazelcast.core.MembershipListener; import com.hazelcast.core.Message; import com.hazelcast.core.MessageListener; import com.hazelcast.map.listener.EntryAddedListener; import com.hazelcast.map.listener.EntryRemovedListener; import com.hazelcast.map.listener.EntryUpdatedListener; import edu.stanford.slac.archiverappliance.PB.data.PBTypeSystem; /** * This is the default config service for the archiver appliance. * There is a subclass that is used in the junit tests. * * @author mshankar * */ public class DefaultConfigService implements ConfigService { private static Logger logger = Logger.getLogger(DefaultConfigService.class.getName()); private static Logger configlogger = Logger.getLogger("config." + DefaultConfigService.class.getName()); private static Logger clusterLogger = Logger.getLogger("cluster." + DefaultConfigService.class.getName()); public static final String SITE_FOR_UNIT_TESTS_NAME = "org.epics.archiverappliance.config.site"; public static final String SITE_FOR_UNIT_TESTS_VALUE = "tests"; /** * Add a property in archappl.properties under this key to identify the class that implements the PVNameToKeyMapping for this installation. */ public static final String ARCHAPPL_PVNAME_TO_KEY_MAPPING_CLASSNAME = "org.epics.archiverappliance.config.DefaultConfigService.PVName2KeyMappingClassName"; // Configuration state begins here. protected String myIdentity; protected ApplianceInfo myApplianceInfo = null; protected Map<String, ApplianceInfo> appliances = null; // Persisted state begins here. protected Map<String, PVTypeInfo> typeInfos = null; protected Map<String, UserSpecifiedSamplingParams> archivePVRequests = null; protected Map<String, String> channelArchiverDataServers = null; protected Map<String, String> aliasNamesToRealNames = null; // These are not persisted but derived from other info protected Map<String, ApplianceInfo> pv2appliancemapping = null; protected Map<String, String> clusterInet2ApplianceIdentity = null; protected Map<String, List<ChannelArchiverDataServerPVInfo>> pv2ChannelArchiverDataServer = null; protected ITopic<PubSubEvent> pubSub = null; protected Map<String, Boolean> namedFlags = null; // Configuration state ends here. // Runtime state begins here protected LinkedList<Runnable> shutdownHooks = new LinkedList<Runnable>(); protected PBThreeTierETLPVLookup etlPVLookup = null; protected RetrievalState retrievalState = null; protected MgmtRuntimeState mgmtRuntime = null;; protected EngineContext engineContext = null; protected ConcurrentSkipListSet<String> appliancesInCluster = new ConcurrentSkipListSet<String>(); // Runtime state ends here // This is an optimization; we cache a copy of PVs that are registered for this appliance. protected ConcurrentSkipListSet<String> pvsForThisAppliance = null; // Maintain a TRIE index for the pvNames in this appliance. protected ConcurrentHashMap<String, ConcurrentSkipListSet<String>> parts2PVNamesForThisAppliance = new ConcurrentHashMap<String, ConcurrentSkipListSet<String>>(); protected ConcurrentSkipListSet<String> pausedPVsForThisAppliance = null; protected ApplianceAggregateInfo applianceAggregateInfo = new ApplianceAggregateInfo(); protected EventBus eventBus = new AsyncEventBus(Executors.newSingleThreadExecutor(new ThreadFactory() { @Override public Thread newThread(Runnable r) { return new Thread(r, "Event bus");}})); protected Properties archapplproperties = new Properties(); protected PVNameToKeyMapping pvName2KeyConverter = null; protected ConfigPersistence persistanceLayer; // State local to DefaultConfigService. protected WAR_FILE warFile = WAR_FILE.MGMT; protected STARTUP_SEQUENCE startupState = STARTUP_SEQUENCE.ZEROTH_STATE; protected ScheduledExecutorService startupExecutor = null; protected ProcessMetrics processMetrics = new ProcessMetrics(); private HashSet<String> runTimeFields = new HashSet<String>(); // Use a Guava cache to store one and only one ExecutePolicy object that expires after some inactivity. // The side effect is that it may take this many minutes to update the policy that is cached. private LoadingCache<String, ExecutePolicy> theExecutionPolicy = CacheBuilder.newBuilder() .expireAfterWrite(1, TimeUnit.MINUTES) .removalListener(new RemovalListener<String, ExecutePolicy>() { @Override public void onRemoval(RemovalNotification<String, ExecutePolicy> arg) { arg.getValue().close(); } }) .build(new CacheLoader<String, ExecutePolicy>() { public ExecutePolicy load(String key) throws IOException { logger.info("Updating the cached execute policy"); return new ExecutePolicy(DefaultConfigService.this); } }); private ServletContext servletContext; protected DefaultConfigService() { // Only the unit tests config service uses this constructor. } @Override public void initialize(ServletContext sce) throws ConfigException { this.servletContext = sce; String contextPath = sce.getContextPath(); logger.info("DefaultConfigService was created with a servlet context " + contextPath); try { String pathToVersionTxt = sce.getRealPath("ui/comm/version.txt"); logger.debug("The full path to the version.txt is " + pathToVersionTxt); List<String> lines = Files.readAllLines(Paths.get(pathToVersionTxt), Charset.forName("UTF-8")); for(String line : lines) { configlogger.info(line); } } catch(Throwable t) { logger.fatal("Unable to determine appliance version", t); } try { // We first try Java system properties for this appliance's identity // If a property is not defined, then we check the environment. // This gives us the ability to cater to unit tests as well as running using buildAndDeploy scripts without touching the server.xml file. // Probably not the most standard way but suited to this need. // Finally, we use the local machine's hostname as the myidentity. myIdentity = System.getProperty(ARCHAPPL_MYIDENTITY); if(myIdentity == null) { myIdentity = System.getenv(ARCHAPPL_MYIDENTITY); if(myIdentity != null) { logger.info("Obtained my identity from environment variable " + myIdentity); } else { logger.info("Using the local machine's hostname " + myIdentity + " as my identity"); myIdentity = InetAddress.getLocalHost().getCanonicalHostName(); } if(myIdentity == null) { throw new ConfigException("Unable to determine identity of this appliance"); } } else { logger.info("Obtained my identity from Java system properties " + myIdentity); } logger.info("My identity is " + myIdentity); } catch(Exception ex) { String msg = "Cannot determine this appliance's identity using either the environment variable " + ARCHAPPL_MYIDENTITY + " or the java system property " + ARCHAPPL_MYIDENTITY; configlogger.fatal(msg); throw new ConfigException(msg, ex); } // Appliances should be local and come straight from persistence. try { appliances = AppliancesList.loadAppliancesXML(servletContext); } catch(Exception ex) { throw new ConfigException("Exception loading appliances.xml", ex); } myApplianceInfo = appliances.get(myIdentity); if(myApplianceInfo == null) throw new ConfigException("Unable to determine applianceinfo using identity " + myIdentity); configlogger.info("My identity is " + myApplianceInfo.getIdentity() + " and my mgmt URL is " + myApplianceInfo.getMgmtURL()); // To make sure we are not starting multiple appliance with the same identity, we make sure that the hostnames match try { String machineHostName = InetAddress.getLocalHost().getCanonicalHostName(); String[] myAddrParts = myApplianceInfo.getClusterInetPort().split(":"); String myHostNameFromInfo = myAddrParts[0]; if(myHostNameFromInfo.equals("localhost")) { logger.debug("Using localhost for the cluster inet port. If you are indeed running a cluster, the cluster members will not join the cluster."); } else if (myHostNameFromInfo.equals(machineHostName)) { logger.debug("Hostname from config and hostname from InetAddress match exactly; we are correctly configured " + machineHostName); } else if(InetAddressValidator.getInstance().isValid(myHostNameFromInfo)) { logger.debug("Using ipAddress for cluster config " + myHostNameFromInfo); } else { String msg = "The hostname from appliances.xml is " + myHostNameFromInfo + " and from a call to InetAddress.getLocalHost().getCanonicalHostName() (typially FQDN) is " + machineHostName + ". These are not identical. They are probably equivalent but to prevent multiple appliances binding to the same identity we enforce this equality."; configlogger.fatal(msg); throw new ConfigException(msg); } } catch(UnknownHostException ex) { configlogger.error("Got an UnknownHostException when trying to determine the hostname. This happens when DNS is not set correctly on this machine (for example, when using VM's. See the documentation for InetAddress.getLocalHost().getCanonicalHostName()"); } try { String archApplPropertiesFileName = System.getProperty(ARCHAPPL_PROPERTIES_FILENAME); if(archApplPropertiesFileName == null) { archApplPropertiesFileName = System.getenv(ARCHAPPL_PROPERTIES_FILENAME); } if(archApplPropertiesFileName == null) { archApplPropertiesFileName = new URL(this.getClass().getClassLoader().getResource(DEFAULT_ARCHAPPL_PROPERTIES_FILENAME).toString()).getPath(); configlogger.info("Loading archappl.properties from the webapp classpath " + archApplPropertiesFileName); } else { configlogger.info("Loading archappl.properties using the environment/JVM property from " + archApplPropertiesFileName); } try(InputStream is = new FileInputStream(new File(archApplPropertiesFileName))) { archapplproperties.load(is); configlogger.info("Done loading installation specific properties file from " + archApplPropertiesFileName); } catch(Exception ex) { throw new ConfigException("Exception loading installation specific properties file " + archApplPropertiesFileName, ex); } } catch(ConfigException cex) { throw cex; } catch(Exception ex) { configlogger.fatal("Exception loading the appliance properties file", ex); } switch(contextPath) { case "/mgmt": warFile = WAR_FILE.MGMT; this.mgmtRuntime = new MgmtRuntimeState(this); break; case "/engine": warFile = WAR_FILE.ENGINE; this.engineContext=new EngineContext(this); break; case "/retrieval": warFile = WAR_FILE.RETRIEVAL; this.retrievalState = new RetrievalState(this); break; case "/etl": this.etlPVLookup = new PBThreeTierETLPVLookup(this); warFile = WAR_FILE.ETL; break; default: logger.error("We seem to have introduced a new component into the system " + contextPath); } String pvName2KeyMappingClass = this.getInstallationProperties().getProperty(ARCHAPPL_PVNAME_TO_KEY_MAPPING_CLASSNAME); if(pvName2KeyMappingClass == null || pvName2KeyMappingClass.equals("") || pvName2KeyMappingClass.length() < 1) { logger.info("Using the default key mapping class"); pvName2KeyConverter = new ConvertPVNameToKey(); pvName2KeyConverter.initialize(this); } else { try { logger.info("Using " + pvName2KeyMappingClass + " as the name to key mapping class"); pvName2KeyConverter = (PVNameToKeyMapping) Class.forName(pvName2KeyMappingClass).newInstance(); pvName2KeyConverter.initialize(this); } catch(Exception ex) { logger.fatal("Cannot initialize pv name to key mapping class " + pvName2KeyMappingClass, ex); throw new ConfigException("Cannot initialize pv name to key mapping class " + pvName2KeyMappingClass, ex); } } String runtimeFieldsListStr = this.getInstallationProperties().getProperty("org.epics.archiverappliance.config.RuntimeKeys"); if(runtimeFieldsListStr != null && !runtimeFieldsListStr.isEmpty()) { logger.debug("Got runtime fields from the properties file " + runtimeFieldsListStr); String[] runTimeFieldsArr = runtimeFieldsListStr.split(","); for(String rf : runTimeFieldsArr) { this.runTimeFields.add(rf.trim()); } } startupExecutor = Executors.newScheduledThreadPool(1, new ThreadFactory() { @Override public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setName("Startup executor"); return t; } }); this.addShutdownHook(new Runnable() { @Override public void run() { logger.info("Shutting down startup scheduled executor..."); startupExecutor.shutdown(); } }); this.startupState = STARTUP_SEQUENCE.READY_TO_JOIN_APPLIANCE; if(this.warFile == WAR_FILE.MGMT) { logger.info("Scheduling webappReady's for the mgmt webapp "); MgmtPostStartup mgmtPostStartup = new MgmtPostStartup(this); ScheduledFuture<?> postStartupFuture = startupExecutor.scheduleAtFixedRate(mgmtPostStartup, 10, 20, TimeUnit.SECONDS); mgmtPostStartup.setCancellingFuture(postStartupFuture); } else { logger.info("Scheduling webappReady's for the non-mgmt webapp " + this.warFile.toString()); NonMgmtPostStartup nonMgmtPostStartup = new NonMgmtPostStartup(this, this.warFile.toString()); ScheduledFuture<?> postStartupFuture = startupExecutor.scheduleAtFixedRate(nonMgmtPostStartup, 10, 20, TimeUnit.SECONDS); nonMgmtPostStartup.setCancellingFuture(postStartupFuture); } // Measure some JMX metrics once a minute startupExecutor.scheduleAtFixedRate(new Runnable() { @Override public void run() { processMetrics.takeMeasurement(); } }, 60, 60, TimeUnit.SECONDS); } /* (non-Javadoc) * @see org.epics.archiverappliance.config.ConfigService#postStartup() */ @Override public void postStartup() throws ConfigException { if(this.startupState != STARTUP_SEQUENCE.READY_TO_JOIN_APPLIANCE) { configlogger.info("Webapp is not in correct state for postStartup " + this.getWarFile().toString() + ". It is in " + this.startupState.toString()); return; } this.startupState = STARTUP_SEQUENCE.POST_STARTUP_RUNNING; configlogger.info("Post startup for " + this.getWarFile().toString()); // Inherit logging from log4j configuration. try { PlatformLoggingMXBean logging = ManagementFactory.getPlatformMXBean(PlatformLoggingMXBean.class); if(logging != null) { java.util.logging.Logger.getLogger("com.hazelcast"); if(clusterLogger.isDebugEnabled()) { logging.setLoggerLevel("com.hazelcast", java.util.logging.Level.FINE.toString()); } else if(clusterLogger.isInfoEnabled()) { logging.setLoggerLevel("com.hazelcast", java.util.logging.Level.INFO.toString()); } else { logger.info("Setting clustering logging based on log levels for cluster." + getClass().getName()); logging.setLoggerLevel("com.hazelcast", java.util.logging.Level.SEVERE.toString()); } } Logger hzMain = Logger.getLogger("com.hazelcast"); if(clusterLogger.isDebugEnabled()) { hzMain.setLevel(Level.DEBUG); } else if(clusterLogger.isInfoEnabled()) { hzMain.setLevel(Level.INFO); } else { logger.info("Setting clustering logging based on log levels for cluster." + getClass().getName()); hzMain.setLevel(Level.FATAL); } } catch(Exception ex) { logger.error("Exception setting logging JVM levels ", ex); } // Add this to the system props before doing anything with Hz System.getProperties().put("hazelcast.logging.type", "log4j"); HazelcastInstance hzinstance = null; // Set the thread count to control how may threads this library spawns. Properties hzThreadCounts = new Properties(); if(System.getenv().containsKey("ARCHAPPL_ALL_APPS_ON_ONE_JVM")) { logger.info("Reducing the generic clustering thread counts."); hzThreadCounts.put("hazelcast.clientengine.thread.count", "2"); hzThreadCounts.put("hazelcast.operation.generic.thread.count", "2"); hzThreadCounts.put("hazelcast.operation.thread.count", "2"); } if(this.warFile == WAR_FILE.MGMT) { // The management webapps are the head honchos in the cluster. We set them up differently configlogger.debug("Initializing the MGMT webapp's clustering"); // If we have a hazelcast.xml in the servlet classpath, the XmlConfigBuilder picks that up. // If not we use the default config found in hazelcast.jar // We then alter this config to suit our purposes. Config config = new XmlConfigBuilder().build(); try { if(this.getClass().getResource("hazelcast.xml") == null) { logger.info("We override the default cluster config by disabling multicast discovery etc."); // We do not use multicast as it is not supported on all networks. config.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false); // We use TCPIP to discover the members in the cluster. // This is part of the config that comes from appliance.xml config.getNetworkConfig().getJoin().getTcpIpConfig().setEnabled(true); // Clear any tcpip config that comes from the default config // This gets rid of the localhost in the default that prevents clusters from forming.. // If we need localhost, we'll add it back later. config.getNetworkConfig().getJoin().getTcpIpConfig().clear(); // Enable interfaces; we seem to need this after 2.4 for clients to work correctly in a multi-homed environment. // We'll add the actual interface later below config.getNetworkConfig().getInterfaces().setEnabled(true); config.getNetworkConfig().getInterfaces().clear(); // We don't really use the authentication provided by the tool; however, we set it to some default config.getGroupConfig().setName("archappl"); config.getGroupConfig().setPassword("archappl"); // Backup count is 1 by default; we set it explicitly however... config.getMapConfig("default").setBackupCount(1); config.setProperty("hazelcast.logging.type", "log4j"); } else { logger.debug("There is a hazelcast.xml in the classpath; skipping default configuration in the code."); } } catch(Exception ex) { throw new ConfigException("Exception configuring cluster", ex); } config.setInstanceName(myIdentity); if(!hzThreadCounts.isEmpty()) { logger.info("Reducing the generic clustering thread counts."); config.getProperties().putAll(hzThreadCounts); } try { String[] myAddrParts = myApplianceInfo.getClusterInetPort().split(":"); String myHostName = myAddrParts[0]; InetAddress myInetAddr = InetAddress.getByName(myHostName); if(!myHostName.equals("localhost") && myInetAddr.isLoopbackAddress()) { logger.info("Address for this appliance -- " + myInetAddr.toString() + " is a loopback address. Changing this to 127.0.0.1 to clustering happy"); myInetAddr = InetAddress.getByName("127.0.0.1"); } int myClusterPort = Integer.parseInt(myAddrParts[1]); logger.debug("We do not let the port auto increment for the MGMT webap"); config.getNetworkConfig().setPortAutoIncrement(false); config.getNetworkConfig().setPort(myClusterPort); config.getNetworkConfig().getInterfaces().addInterface(myInetAddr.getHostAddress()); configlogger.info("Setting my cluster port base to " + myClusterPort + " and using interface " + myInetAddr.getHostAddress()); for(ApplianceInfo applInfo : appliances.values()) { if(applInfo.getIdentity().equals(myIdentity) && this.warFile == WAR_FILE.MGMT) { logger.debug("Not adding myself to the discovery process when I am the mgmt webapp"); } else { String[] addressparts = applInfo.getClusterInetPort().split(":"); String inetaddrpart = addressparts[0]; try { InetAddress inetaddr = InetAddress.getByName(inetaddrpart); if(!inetaddrpart.equals("localhost") && inetaddr.isLoopbackAddress()) { logger.info("Address for appliance " + applInfo.getIdentity() + " - "+ inetaddr.toString() + " is a loopback address. Changing this to 127.0.0.1 to clustering happy"); inetaddr = InetAddress.getByName("127.0.0.1"); } int clusterPort = Integer.parseInt(addressparts[1]); logger.info("Adding " + applInfo.getIdentity() + " from appliances.xml to the cluster discovery using cluster inetport " + inetaddr.toString() + ":" + clusterPort); config.getNetworkConfig().getJoin().getTcpIpConfig().addMember(inetaddr.getHostAddress() + ":" + clusterPort); } catch(UnknownHostException ex) { configlogger.info("Cannnot resolve the IP address for appliance " + inetaddrpart + ". Skipping adding this appliance to the cliuster."); } } } hzinstance = Hazelcast.newHazelcastInstance(config); } catch(Exception ex) { throw new ConfigException("Exception adding member to cluster", ex); } } else { // All other webapps are "native" clients. try { configlogger.debug("Initializing a non-mgmt webapp's clustering"); ClientConfig clientConfig = new ClientConfig(); clientConfig.getGroupConfig().setName("archappl"); clientConfig.getGroupConfig().setPassword("archappl"); clientConfig.setExecutorPoolSize(4); // Non mgmt client can only connect to their MGMT webapp. String[] myAddrParts = myApplianceInfo.getClusterInetPort().split(":"); String myHostName = myAddrParts[0]; InetAddress myInetAddr = InetAddress.getByName(myHostName); if(!myHostName.equals("localhost") && myInetAddr.isLoopbackAddress()) { logger.info("Address for this appliance -- " + myInetAddr.toString() + " is a loopback address. Changing this to 127.0.0.1 to clustering happy"); myInetAddr = InetAddress.getByName("127.0.0.1"); } int myClusterPort = Integer.parseInt(myAddrParts[1]); configlogger.debug(this.warFile + " connecting as a native client to " + myInetAddr.getHostAddress() + ":" + myClusterPort); clientConfig.getNetworkConfig().addAddress(myInetAddr.getHostAddress() + ":" + myClusterPort); clientConfig.setProperty("hazelcast.logging.type", "log4j"); if(!hzThreadCounts.isEmpty()) { logger.info("Reducing the generic clustering thread counts."); clientConfig.getProperties().putAll(hzThreadCounts); } if(!clusterLogger.isDebugEnabled()) { // The client code logs some SEVERE exceptions on shutdown when deploying on the same Tomcat container. // These exceptions are confusing; ideally, we would not have to set the log levels like so. Logger.getLogger("com.hazelcast.client.spi.impl.ClusterListenerThread").setLevel(Level.OFF); Logger.getLogger("com.hazelcast.client.spi.ClientPartitionService").setLevel(Level.OFF); } hzinstance = HazelcastClient.newHazelcastClient(clientConfig); } catch(Exception ex) { throw new ConfigException("Exception adding client to cluster", ex); } } pv2appliancemapping = hzinstance.getMap("pv2appliancemapping"); namedFlags = hzinstance.getMap("namedflags"); typeInfos = hzinstance.getMap("typeinfo"); archivePVRequests = hzinstance.getMap("archivePVRequests"); channelArchiverDataServers = hzinstance.getMap("channelArchiverDataServers"); clusterInet2ApplianceIdentity = hzinstance.getMap("clusterInet2ApplianceIdentity"); aliasNamesToRealNames = hzinstance.getMap("aliasNamesToRealNames"); pv2ChannelArchiverDataServer = hzinstance.getMap("pv2ChannelArchiverDataServer"); pubSub = hzinstance.getTopic("pubSub"); final HazelcastInstance shutdownHzInstance = hzinstance; shutdownHooks.add(0, new Runnable() { @Override public void run() { logger.debug("Shutting down clustering instance in webapp " + warFile.toString()); shutdownHzInstance.shutdown(); } }); if(this.warFile == WAR_FILE.MGMT) { Cluster cluster = hzinstance.getCluster(); String localInetPort = getMemberKey(cluster.getLocalMember()); clusterInet2ApplianceIdentity.put(localInetPort, myIdentity); logger.debug("Adding myself " + myIdentity + " as having inetport " + localInetPort); hzinstance.getMap("clusterInet2ApplianceIdentity").addEntryListener(new EntryAddedListener<Object, Object>() { @Override public void entryAdded(EntryEvent<Object, Object> event) { String appliden = (String) event.getValue(); appliancesInCluster.add(appliden); logger.info("Adding appliance " + appliden + " to the list of active appliances as inetport " + ((String) event.getKey())); } }, true); hzinstance.getMap("clusterInet2ApplianceIdentity").addEntryListener(new EntryRemovedListener<Object, Object>() { @Override public void entryRemoved(EntryEvent<Object, Object> event) { String appliden = (String) event.getValue(); appliancesInCluster.remove(appliden); logger.info("Removing appliance " + appliden + " from the list of active appliancesas inetport " + ((String) event.getKey())); } }, true); logger.debug("Establishing a cluster membership listener to detect when appliances drop off the cluster"); cluster.addMembershipListener(new MembershipListener(){ public void memberAdded(MembershipEvent membersipEvent) { Member member = membersipEvent.getMember(); String inetPort = getMemberKey(member); if(clusterInet2ApplianceIdentity.containsKey(inetPort)) { String appliden = clusterInet2ApplianceIdentity.get(inetPort); appliancesInCluster.add(appliden); configlogger.info("Adding newly started appliance " + appliden + " to the list of active appliances for inetport " + inetPort); } else { logger.debug("Skipping adding appliance using inetport " + inetPort + " to the list of active instances as we do not have a mapping to its identity"); } } public void memberRemoved(MembershipEvent membersipEvent) { Member member = membersipEvent.getMember(); String inetPort = getMemberKey(member); if(clusterInet2ApplianceIdentity.containsKey(inetPort)) { String appliden = clusterInet2ApplianceIdentity.get(inetPort); appliancesInCluster.remove(appliden); configlogger.info("Removing appliance " + appliden + " from the list of active appliances"); } else { configlogger.debug("Received member removed event for " + inetPort); } } @Override public void memberAttributeChanged(MemberAttributeEvent membersipEvent) { Member member = membersipEvent.getMember(); String inetPort = getMemberKey(member); configlogger.debug("Received membership attribute changed event for " + inetPort); } }); logger.debug("Adding the current members in the cluster after establishing the cluster membership listener"); for (Member member : cluster.getMembers()) { String mbrInetPort = getMemberKey(member); logger.debug("Found member " + mbrInetPort); if(clusterInet2ApplianceIdentity.containsKey(mbrInetPort)) { String appliden = clusterInet2ApplianceIdentity.get(mbrInetPort); appliancesInCluster.add(appliden); logger.info("Adding appliance " + appliden + " to the list of active appliances for inetport " + mbrInetPort); } else { logger.debug("Skipping adding appliance using inetport " + mbrInetPort + " to the list of active instances as we do not have a mapping to its identity"); } } logger.info("Established subscription(s) for appliance availability"); if(this.getInstallationProperties().containsKey(ARCHAPPL_NAMEDFLAGS_PROPERTIES_FILE_PROPERTY)) { String namedFlagsFileName = (String) this.getInstallationProperties().get(ARCHAPPL_NAMEDFLAGS_PROPERTIES_FILE_PROPERTY); configlogger.info("Loading named flags from file " + namedFlagsFileName); File namedFlagsFile = new File(namedFlagsFileName); if(!namedFlagsFile.exists()) { configlogger.error("File containing named flags " + namedFlagsFileName + " specified but not present"); } else { Properties namedFlagsFromFile = new Properties(); try(FileInputStream is = new FileInputStream(namedFlagsFile)) { namedFlagsFromFile.load(is); for(Object namedFlagFromFile : namedFlagsFromFile.keySet()) { try { String namedFlagFromFileStr = (String) namedFlagFromFile; Boolean namedFlagFromFileValue = Boolean.parseBoolean((String)namedFlagsFromFile.get(namedFlagFromFileStr)); logger.debug("Setting named flag " + namedFlagFromFileStr + " to " + namedFlagFromFileValue); this.namedFlags.put(namedFlagFromFileStr, namedFlagFromFileValue); } catch(Exception ex) { logger.error("Exception loading named flag from file" + namedFlagsFileName, ex); } } } catch(Exception ex) { configlogger.error("Exception loading named flags from " + namedFlagsFileName, ex); } } } } if(this.warFile == WAR_FILE.ENGINE) { // It can take a while for the engine to start up. // We probably want to do this in the background so that the appliance as a whole starts up quickly and we get retrieval up and running quickly. this.startupExecutor.schedule(new Runnable() { @Override public void run() { try { logger.debug("Starting up the engine's channels on startup."); archivePVSonStartup(); logger.debug("Done starting up the engine's channels in startup."); } catch(Throwable t) { configlogger.fatal("Exception starting up the engine channels on startup", t); } } }, 1, TimeUnit.SECONDS); } else if(this.warFile == WAR_FILE.ETL) { this.etlPVLookup.postStartup(); } else if(this.warFile == WAR_FILE.MGMT) { pvsForThisAppliance = new ConcurrentSkipListSet<String>(); pausedPVsForThisAppliance = new ConcurrentSkipListSet<String>(); initializePersistenceLayer(); loadTypeInfosFromPersistence(); loadAliasesFromPersistence(); loadArchiveRequestsFromPersistence(); loadExternalServersFromPersistence(); registerForNewExternalServers(hzinstance.getMap("channelArchiverDataServers")); // Cache the aggregate of all the PVs that are registered to this appliance. logger.debug("Building a local aggregate of PV infos that are registered to this appliance"); for(String pvName : getPVsForThisAppliance()) { if(!pvsForThisAppliance.contains(pvName)) { applianceAggregateInfo.addInfoForPV(pvName, this.getTypeInfoForPV(pvName), this); } } } // Register for changes to the typeinfo map. logger.info("Registering for changes to typeinfos"); hzinstance.getMap("typeinfo").addEntryListener(new EntryAddedListener<Object, Object>() { @Override public void entryAdded(EntryEvent<Object, Object> entryEvent) { logger.debug("Received entryAdded for pvTypeInfo"); PVTypeInfo typeInfo = (PVTypeInfo) entryEvent.getValue(); String pvName = typeInfo.getPvName(); eventBus.post(new PVTypeInfoEvent(pvName, typeInfo, ChangeType.TYPEINFO_ADDED)); if(persistanceLayer != null) { try { persistanceLayer.putTypeInfo(pvName, typeInfo); } catch(Exception ex) { logger.error("Exception persisting pvTypeInfo for pv " + pvName, ex); } } } }, true); hzinstance.getMap("typeinfo").addEntryListener(new EntryRemovedListener<Object, Object>() { @Override public void entryRemoved(EntryEvent<Object, Object> entryEvent) { PVTypeInfo typeInfo =(PVTypeInfo) entryEvent.getOldValue(); String pvName = typeInfo.getPvName(); logger.info("Received entryRemoved for pvTypeInfo " + pvName); eventBus.post(new PVTypeInfoEvent(pvName, typeInfo, ChangeType.TYPEINFO_DELETED)); if(persistanceLayer != null) { try { persistanceLayer.deleteTypeInfo(pvName); } catch(Exception ex) { logger.error("Exception deleting pvTypeInfo for pv " + pvName, ex); } } } }, true); hzinstance.getMap("typeinfo").addEntryListener(new EntryUpdatedListener<Object, Object>() { @Override public void entryUpdated(EntryEvent<Object, Object> entryEvent) { PVTypeInfo typeInfo =(PVTypeInfo) entryEvent.getValue(); String pvName = typeInfo.getPvName(); eventBus.post(new PVTypeInfoEvent(pvName, typeInfo, ChangeType.TYPEINFO_MODIFIED)); logger.debug("Received entryUpdated for pvTypeInfo"); if(persistanceLayer != null) { try { persistanceLayer.putTypeInfo(pvName, typeInfo); } catch(Exception ex) { logger.error("Exception persisting pvTypeInfo for pv " + pvName, ex); } } } }, true); eventBus.register(this); pubSub.addMessageListener(new MessageListener<PubSubEvent>() { @Override public void onMessage(Message<PubSubEvent> pubSubEventMsg) { PubSubEvent pubSubEvent = pubSubEventMsg.getMessageObject(); if(pubSubEvent.getDestination() != null) { if(pubSubEvent.getDestination().equals("ALL") || (pubSubEvent.getDestination().startsWith(myIdentity) && pubSubEvent.getDestination().endsWith(DefaultConfigService.this.warFile.toString())) ) { // We publish messages from hazelcast into this VM only if the intened WAR file is us. logger.debug("Publishing event into this JVM " + pubSubEvent.generateEventDescription()); // In this case, we set the source as being the cluster to prevent republishing back into the cluster. pubSubEvent.markSourceAsCluster(); eventBus.post(pubSubEvent); } else { logger.debug("Skipping publishing event into this JVM " + pubSubEvent.generateEventDescription() + " as destination is not me " + DefaultConfigService.this.warFile.toString()); } } else { logger.debug("Skipping publishing event with null destination"); } } }); logger.info("Done registering for changes to typeinfos"); this.startupState = STARTUP_SEQUENCE.STARTUP_COMPLETE; configlogger.info("Start complete for webapp " + this.warFile); } @Override public STARTUP_SEQUENCE getStartupState() { return this.startupState; } @Subscribe public void updatePVSForThisAppliance(PVTypeInfoEvent event) { if(logger.isDebugEnabled()) logger.debug("Received pvTypeInfo change event for pv " + event.getPvName()); PVTypeInfo typeInfo = event.getTypeInfo(); String pvName = typeInfo.getPvName(); if(typeInfo.getApplianceIdentity().equals(myApplianceInfo.getIdentity())) { if(event.getChangeType() == ChangeType.TYPEINFO_DELETED) { if(pvsForThisAppliance != null) { if(pvsForThisAppliance.contains(pvName)) { logger.debug("Removing pv " + pvName + " from the locally cached copy of pvs for this appliance"); pvsForThisAppliance.remove(pvName); pausedPVsForThisAppliance.remove(pvName); // For now, we do not anticipate many PVs being deleted from the cache to worry about keeping applianceAggregateInfo upto date... // This may change later... String[] parts = this.pvName2KeyConverter.breakIntoParts(pvName); for(String part : parts) { parts2PVNamesForThisAppliance.get(part).remove(pvName); } } } } else { if(pvsForThisAppliance != null) { if(!pvsForThisAppliance.contains(pvName)) { logger.debug("Adding pv " + pvName + " to the locally cached copy of pvs for this appliance"); pvsForThisAppliance.add(pvName); if(typeInfo.isPaused()) { pausedPVsForThisAppliance.add(typeInfo.getPvName()); } String[] parts = this.pvName2KeyConverter.breakIntoParts(pvName); for(String part : parts) { if(!parts2PVNamesForThisAppliance.containsKey(part)) { parts2PVNamesForThisAppliance.put(part, new ConcurrentSkipListSet<String>()); } parts2PVNamesForThisAppliance.get(part).add(pvName); } applianceAggregateInfo.addInfoForPV(pvName, typeInfo, this); } else { if(typeInfo.isPaused()) { pausedPVsForThisAppliance.add(typeInfo.getPvName()); } else { pausedPVsForThisAppliance.remove(typeInfo.getPvName()); } } } } } } @Subscribe public void publishEventIntoCluster(PubSubEvent pubSubEvent) { if(pubSubEvent.isSourceCluster()) { logger.debug("Skipping publishing events from the cluster back into the cluster " + pubSubEvent.generateEventDescription()); return; } if(pubSubEvent.getDestination().startsWith(myIdentity) && pubSubEvent.getDestination().endsWith(this.warFile.toString())) { logger.debug(this.warFile + " - Skipping publishing event " + pubSubEvent.generateEventDescription() + " meant for myself " + this.warFile.toString()); } else { pubSubEvent.setSource(myIdentity); logger.debug(this.warFile + " - Publishing event from local event bus onto cluster " + pubSubEvent.generateEventDescription()); pubSub.publish(pubSubEvent); } } /** * Get the PVs that belong to this appliance and start archiving them * Needless to day, this gets done only in the engine. */ private void archivePVSonStartup() { configlogger.debug("Start archiving PVs from persistence."); int secondsToBuffer = PVTypeInfo.getSecondsToBuffer(this); // To prevent broadcast storms, we pause for pausePerGroup seconds for every pausePerGroup PVs int currentPVCount = 0; int pausePerGroupPVCount = 2000; int pausePerGroupPauseTimeInSeconds = 2; for(String pvName : this.getPVsForThisAppliance()) { try { PVTypeInfo typeInfo = typeInfos.get(pvName); if(typeInfo == null) { logger.error("On restart, cannot find typeinfo for pv " + pvName + ". Not archiving"); continue; } if(typeInfo.isPaused()) { logger.debug("Skipping archiving paused PV " + pvName + " on startup"); continue; } ArchDBRTypes dbrType = typeInfo.getDBRType(); float samplingPeriod = typeInfo.getSamplingPeriod(); SamplingMethod samplingMethod = typeInfo.getSamplingMethod(); StoragePlugin firstDest = StoragePluginURLParser.parseStoragePlugin(typeInfo.getDataStores()[0], this); Timestamp lastKnownTimestamp = typeInfo.determineLastKnownEventFromStores(this); if(logger.isDebugEnabled()) logger.debug("Last known timestamp from ETL stores is for pv " + pvName + " is "+ TimeUtils.convertToHumanReadableString(lastKnownTimestamp)); ArchiveEngine.archivePV(pvName, samplingPeriod, samplingMethod, secondsToBuffer, firstDest, this, dbrType,lastKnownTimestamp, typeInfo.getControllingPV(), typeInfo.getArchiveFields(), typeInfo.getHostName(), typeInfo.isUsePVAccess(), typeInfo.isUseDBEProperties()); currentPVCount++; if(currentPVCount % pausePerGroupPVCount == 0) { logger.debug("Sleeping for " + pausePerGroupPauseTimeInSeconds + " to prevent CA search storms"); Thread.sleep(pausePerGroupPauseTimeInSeconds*1000); } } catch(Throwable t) { logger.error("Exception starting up archiving of PV " + pvName + ". Moving on to the next pv.", t); } } configlogger.debug("Started " + currentPVCount + " PVs from persistence."); } @Override public boolean isStartupComplete() { return startupState == STARTUP_SEQUENCE.STARTUP_COMPLETE; } @Override public Properties getInstallationProperties() { return archapplproperties; } @Override public Collection<ApplianceInfo> getAppliancesInCluster() { ArrayList<ApplianceInfo> sortedAppliances = new ArrayList<ApplianceInfo>(); for(ApplianceInfo info : appliances.values()) { if(appliancesInCluster.contains(info.getIdentity())) { sortedAppliances.add(info); } else { logger.debug("Skipping appliance that is in the persistence but not in the cluster" + info.getIdentity()); } } Collections.sort(sortedAppliances, new Comparator<ApplianceInfo>() { @Override public int compare(ApplianceInfo o1, ApplianceInfo o2) { return o1.getIdentity().compareTo(o2.getIdentity()); } }); return sortedAppliances; } @Override public ApplianceInfo getMyApplianceInfo() { return myApplianceInfo; } @Override public ApplianceInfo getAppliance(String identity) { return appliances.get(identity); } @Override public Collection<String> getAllPVs() { List<PVApplianceCombo> sortedCombos = getSortedPVApplianceCombo(); ArrayList<String> allPVs = new ArrayList<String>(); for(PVApplianceCombo combo : sortedCombos) { allPVs.add(combo.pvName); } return allPVs; } @Override public ApplianceInfo getApplianceForPV(String pvName) { ApplianceInfo applianceInfo = pv2appliancemapping.get(pvName); if(applianceInfo == null && this.persistanceLayer != null) { try { PVTypeInfo typeInfo = this.persistanceLayer.getTypeInfo(pvName); if(typeInfo != null) { applianceInfo = this.getAppliance(typeInfo.getApplianceIdentity()); } } catch(IOException ex) { logger.error("Exception lookin up appliance for pv in persistence", ex); } } return applianceInfo; } @Override public Iterable<String> getPVsForAppliance(ApplianceInfo info) { String identity = info.getIdentity(); List<PVApplianceCombo> sortedCombos = getSortedPVApplianceCombo(); ArrayList<String> pvsForAppliance = new ArrayList<String>(); for(PVApplianceCombo combo : sortedCombos) { if(combo.applianceIdentity.equals(identity)) { pvsForAppliance.add(combo.pvName); } } return pvsForAppliance; } @Override public Iterable<String> getPVsForThisAppliance() { if(pvsForThisAppliance != null) { logger.debug("Returning the locally cached copy of the pvs for this appliance"); return pvsForThisAppliance; } else { logger.debug("Fetching the list of PVs for this appliance from the mgmt app"); JSONArray pvs = GetUrlContent.getURLContentAsJSONArray(myApplianceInfo.getMgmtURL() + "/getPVsForThisAppliance"); LinkedList<String> retval = new LinkedList<String>(); for(Object pv : pvs) { retval.add((String) pv); } return retval; } } @Override public boolean isBeingArchivedOnThisAppliance(String pvName) { boolean isField = PVNames.isField(pvName); String plainPVName = PVNames.stripFieldNameFromPVName(pvName); if(this.pvsForThisAppliance.contains(plainPVName)) { if(isField) { PVTypeInfo typeInfo = this.getTypeInfoForPV(plainPVName); if(typeInfo != null && Arrays.asList(typeInfo.getArchiveFields()).contains(PVNames.getFieldName(pvName))) { return true; } } else { return true; } } if(this.aliasNamesToRealNames.containsKey(plainPVName)) { plainPVName = this.aliasNamesToRealNames.get(plainPVName); if(this.pvsForThisAppliance.contains(plainPVName)) { if(isField) { PVTypeInfo typeInfo = this.getTypeInfoForPV(plainPVName); if(typeInfo != null && Arrays.asList(typeInfo.getArchiveFields()).contains(PVNames.getFieldName(pvName))) { return true; } } else { return true; } } } return false; } @Override public Set<String> getPVsForApplianceMatchingRegex(String nameToMatch) { logger.debug("Finding matching names for " + nameToMatch); LinkedList<String> fixedStringParts = new LinkedList<String>(); String[] parts = this.pvName2KeyConverter.breakIntoParts(nameToMatch); Pattern fixedStringParttern = Pattern.compile("[a-zA-Z_0-9-]+"); for(String part : parts) { if(fixedStringParttern.matcher(part).matches()) { logger.debug("Fixed string part " + part); fixedStringParts.add(part); } else { logger.debug("Regex string part " + part); } } if(fixedStringParts.size() > 0) { HashSet<String> ret = new HashSet<String>(); HashSet<String> namesSubset = new HashSet<String>(); // This reverse is probably specific to SLAC's namespace rules but it does make a big difference. // Perhaps we can use a more intelligent way of choosing the specific path thru the trie. Collections.reverse(fixedStringParts); for(String fixedStringPart : fixedStringParts) { ConcurrentSkipListSet<String> pvNamesForPart = parts2PVNamesForThisAppliance.get(fixedStringPart); if(pvNamesForPart != null) { if(namesSubset.isEmpty()) { namesSubset.addAll(pvNamesForPart); } else { namesSubset.retainAll(pvNamesForPart); } } } logger.debug("Using fixed string path matching against names " + namesSubset.size()); Pattern pattern = Pattern.compile(nameToMatch); for(String pvName : namesSubset) { if(pattern.matcher(pvName).matches()) { ret.add(pvName); } } return ret; } else { // The use pattern did not have any fixed elements at all. // In this case we do brute force matching; should take longer. // This is also not optimal but probably don't want yet another list of PV's Pattern pattern = Pattern.compile(nameToMatch); HashSet<String> allNames = new HashSet<String>(); HashSet<String> ret = new HashSet<String>(); logger.debug("Using brute force pattern matching against names"); for(ConcurrentSkipListSet<String> pvNamesForPart : parts2PVNamesForThisAppliance.values()) { allNames.addAll(pvNamesForPart); } for(String pvName : allNames) { if(pattern.matcher(pvName).matches()) { ret.add(pvName); } } return ret; } } @Override public ApplianceAggregateInfo getAggregatedApplianceInfo(ApplianceInfo applianceInfo) throws IOException { if(applianceInfo.getIdentity().equals(myApplianceInfo.getIdentity()) && this.warFile == WAR_FILE.MGMT) { logger.debug("Returning local copy of appliance info for " + applianceInfo.getIdentity()); return applianceAggregateInfo; } else { try { JSONObject aggregateInfo = GetUrlContent.getURLContentAsJSONObject(applianceInfo.getMgmtURL() + "/getAggregatedApplianceInfo", false); JSONDecoder<ApplianceAggregateInfo> jsonDecoder = JSONDecoder.getDecoder(ApplianceAggregateInfo.class); ApplianceAggregateInfo retval = new ApplianceAggregateInfo(); jsonDecoder.decode(aggregateInfo, retval); return retval; } catch(Exception ex) { throw new IOException(ex); } } } @Override public void registerPVToAppliance(String pvName, ApplianceInfo applianceInfo) throws AlreadyRegisteredException { ApplianceInfo info = pv2appliancemapping.get(pvName); if(info != null) throw new AlreadyRegisteredException(info); pv2appliancemapping.put(pvName, applianceInfo); } @Override public PVTypeInfo getTypeInfoForPV(String pvName) { if(typeInfos.containsKey(pvName)) { logger.debug("Retrieving typeinfo from cache for pv " + pvName); return typeInfos.get(pvName); } return null; } @Override public void updateTypeInfoForPV(String pvName, PVTypeInfo typeInfo) { logger.debug("Updating typeinfo for " + pvName); if(!typeInfo.keyAlreadyGenerated()) { // This call should also typically set the chunk key in the type info. this.pvName2KeyConverter.convertPVNameToKey(pvName); } typeInfos.put(pvName, typeInfo); } @Override public void removePVFromCluster(String pvName) { logger.info("Removing PV from cluster.." + pvName); pv2appliancemapping.remove(pvName); pvsForThisAppliance.remove(pvName); typeInfos.remove(pvName); pausedPVsForThisAppliance.remove(pvName); String[] parts = this.pvName2KeyConverter.breakIntoParts(pvName); for(String part : parts) { ConcurrentSkipListSet<String> pvNamesForPart = parts2PVNamesForThisAppliance.get(part); if(pvNamesForPart != null) { pvNamesForPart.remove(pvName); } } } private class PVApplianceCombo implements Comparable<PVApplianceCombo> { String applianceIdentity; String pvName; public PVApplianceCombo(String applianceIdentity, String pvName) { this.applianceIdentity = applianceIdentity; this.pvName = pvName; } @Override public int compareTo(PVApplianceCombo other) { if(this.applianceIdentity.equals(other.applianceIdentity)) { return this.pvName.compareTo(other.pvName); } else { return this.applianceIdentity.compareTo(other.applianceIdentity); } } } private List<PVApplianceCombo> getSortedPVApplianceCombo() { ArrayList<PVApplianceCombo> sortedCombos = new ArrayList<PVApplianceCombo>(); for(Map.Entry<String, ApplianceInfo> entry : pv2appliancemapping.entrySet()) { sortedCombos.add(new PVApplianceCombo(entry.getValue().getIdentity(), entry.getKey())); } Collections.sort(sortedCombos); return sortedCombos; } @Override public void addToArchiveRequests(String pvName, UserSpecifiedSamplingParams userSpecifiedSamplingParams) { archivePVRequests.put(pvName, userSpecifiedSamplingParams); try { persistanceLayer.putArchivePVRequest(pvName, userSpecifiedSamplingParams); } catch(IOException ex) { logger.error("Exception adding request to persistence", ex); } } @Override public void updateArchiveRequest(String pvName, UserSpecifiedSamplingParams userSpecifiedSamplingParams) { try { if(persistanceLayer.getArchivePVRequest(pvName) != null) { archivePVRequests.put(pvName, userSpecifiedSamplingParams); persistanceLayer.putArchivePVRequest(pvName, userSpecifiedSamplingParams); } else { logger.error("Do not have user specified params for pv " + pvName + " in this appliance. Not updating."); } } catch(IOException ex) { logger.error("Exception updating request in persistence", ex); } } @Override public Set<String> getArchiveRequestsCurrentlyInWorkflow() { return new HashSet<String>(archivePVRequests.keySet()); } @Override public boolean doesPVHaveArchiveRequestInWorkflow(String pvname) { return archivePVRequests.containsKey(pvname); } @Override public UserSpecifiedSamplingParams getUserSpecifiedSamplingParams(String pvName) { return archivePVRequests.get(pvName); } @Override public void archiveRequestWorkflowCompleted(String pvName) { archivePVRequests.remove(pvName); try { persistanceLayer.removeArchivePVRequest(pvName); } catch(IOException ex) { logger.error("Exception removing request from persistence", ex); } } @Override public int getInitialDelayBeforeStartingArchiveRequestWorkflow() { int appliancesInCluster = 0; for(@SuppressWarnings("unused") ApplianceInfo info : this.getAppliancesInCluster()) { appliancesInCluster++; } int initialDelayInSeconds = 10; if(appliancesInCluster > 1) { // We use a longer initial delay here to get all the appliances in the cluster a chance to restart initialDelayInSeconds = 30*60; } return initialDelayInSeconds; } @Override public void addAlias(String aliasName, String realName) { aliasNamesToRealNames.put(aliasName, realName); try { persistanceLayer.putAliasNamesToRealName(aliasName, realName); } catch(IOException ex) { logger.error("Exception adding alias name to persistence " + aliasName, ex); } // Add aliases into the trie String[] parts = this.pvName2KeyConverter.breakIntoParts(aliasName); for(String part : parts) { if(!parts2PVNamesForThisAppliance.containsKey(part)) { parts2PVNamesForThisAppliance.put(part, new ConcurrentSkipListSet<String>()); } parts2PVNamesForThisAppliance.get(part).add(aliasName); } } @Override public void removeAlias(String aliasName, String realName) { aliasNamesToRealNames.remove(aliasName); try { persistanceLayer.removeAliasName(aliasName, realName); } catch(IOException ex) { logger.error("Exception removing alias name from persistence " + aliasName, ex); } // Remove the aliasname from the trie String[] parts = this.pvName2KeyConverter.breakIntoParts(aliasName); for(String part : parts) { parts2PVNamesForThisAppliance.get(part).remove(aliasName); } } @Override public String getRealNameForAlias(String aliasName) { return aliasNamesToRealNames.get(aliasName); } @Override public List<String> getAllAliases() { return new ArrayList<String>(aliasNamesToRealNames.keySet()); } private final String[] extraFields = new String[] {"MDEL","ADEL", "SCAN","RTYP"}; @Override public String[] getExtraFields() { return extraFields; } @Override public Set<String> getRuntimeFields() { return runTimeFields; } @Override public PBThreeTierETLPVLookup getETLLookup() { return etlPVLookup; } @Override public RetrievalState getRetrievalRuntimeState() { return retrievalState; } @Override public boolean isShuttingDown() { return startupExecutor.isShutdown(); } @Override public void addShutdownHook(Runnable runnable) { shutdownHooks.add(runnable); } private void runShutDownHooksAndCleanup() { LinkedList<Runnable> shutDnHooks = new LinkedList<Runnable>(this.shutdownHooks); Collections.reverse(shutDnHooks); logger.debug("Running shutdown hooks in webapp " + this.warFile); for(Runnable shutdownHook : shutDnHooks) { try { shutdownHook.run(); } catch(Throwable t) { logger.warn("Exception shutting down service using shutdown hook " + shutdownHook.toString(), t); } } logger.debug("Done running shutdown hooks in webapp " + this.warFile); } @Override public void shutdownNow() { this.runShutDownHooksAndCleanup(); } @Override public Map<String, String> getExternalArchiverDataServers() { return channelArchiverDataServers; } @Override public void addExternalArchiverDataServer(String serverURL, String archivesCSV) throws IOException { String[] archives = archivesCSV.split(","); boolean loadCAPVs = false; if(!this.getExternalArchiverDataServers().containsKey(serverURL)) { this.getExternalArchiverDataServers().put(serverURL, archivesCSV); loadCAPVs = true; } else { logger.info(serverURL + " already exists in the map. So, skipping loading PVs from the external server."); } // We always add to persistence; whether this is from the UI or from the other appliances in the cluster. if(this.persistanceLayer != null) { persistanceLayer.putExternalDataServer(serverURL, archivesCSV); } try { // We load PVs from the external server only if this is the first server starting up... if(loadCAPVs) { for(int i = 0; i < archives.length; i++) { String archive = archives[i]; loadExternalArchiverPVs(serverURL, archive); } } } catch(Exception ex) { logger.error("Exception adding Channel Archiver archives " + serverURL + " - " + archivesCSV, ex); throw new IOException(ex); } } @Override public void removeExternalArchiverDataServer(String serverURL, String archivesCSV) throws IOException { this.getExternalArchiverDataServers().remove(serverURL); // We always add to persistence; whether this is from the UI or from the other appliances in the cluster. if(this.persistanceLayer != null) { logger.info("Removing the channel archiver server " + serverURL + " from the persistent store."); persistanceLayer.removeExternalDataServer(serverURL, archivesCSV); } } /** * Given a external Archiver data server URL and an archive; * If this is a ChannelArchiver (archives != pbraw); * this adds the PVs in the Channel Archiver so that they can be proxied. * @param serverURL * @param archive * @throws IOException * @throws SAXException */ private void loadExternalArchiverPVs(String serverURL, String archive) throws IOException, SAXException { if(archive.equals("pbraw")) { logger.debug("We do not load PV names from external EPICS archiver appliances. These can number in the multiple millions and the respone on retrieval is fast enough anyways"); return; } ChannelArchiverDataServerInfo serverInfo = new ChannelArchiverDataServerInfo(serverURL, archive); NamesHandler handler = new NamesHandler(); logger.debug("Getting list of PV's from Channel Archiver Server at " + serverURL + " using index " + archive); XMLRPCClient.archiverNames(serverURL, archive, handler); HashMap<String, List<ChannelArchiverDataServerPVInfo>> tempPVNames = new HashMap<String, List<ChannelArchiverDataServerPVInfo>>(); long totalPVsProxied = 0; for(NamesHandler.ChannelDescription pvChannelDesc : handler.getChannels()) { String pvName = PVNames.normalizePVName(pvChannelDesc.getName()); if(this.pv2ChannelArchiverDataServer.containsKey(pvName)) { List<ChannelArchiverDataServerPVInfo> alreadyExistingServers = this.pv2ChannelArchiverDataServer.get(pvName); logger.debug("Adding new server to already existing ChannelArchiver server for " + pvName); addExternalCAServerToExistingList(alreadyExistingServers, serverInfo, pvChannelDesc); tempPVNames.put(pvName, alreadyExistingServers); } else if(tempPVNames.containsKey(pvName)) { List<ChannelArchiverDataServerPVInfo> alreadyExistingServers = tempPVNames.get(pvName); logger.debug("Adding new server to already existing ChannelArchiver server (in tempspace) for " + pvName); addExternalCAServerToExistingList(alreadyExistingServers, serverInfo, pvChannelDesc); tempPVNames.put(pvName, alreadyExistingServers); } else { List<ChannelArchiverDataServerPVInfo> caServersForPV = new ArrayList<ChannelArchiverDataServerPVInfo>(); caServersForPV.add(new ChannelArchiverDataServerPVInfo(serverInfo, pvChannelDesc.getStartSec(), pvChannelDesc.getEndSec())); tempPVNames.put(pvName, caServersForPV); } if(tempPVNames.size() > 1000) { this.pv2ChannelArchiverDataServer.putAll(tempPVNames); totalPVsProxied += tempPVNames.size(); tempPVNames.clear(); } } if(!tempPVNames.isEmpty()) { this.pv2ChannelArchiverDataServer.putAll(tempPVNames); totalPVsProxied += tempPVNames.size(); tempPVNames.clear(); } if(logger.isDebugEnabled()) logger.debug("Proxied a total of " + totalPVsProxied + " from server " + serverURL + " using archive " + archive); } private static void addExternalCAServerToExistingList(List<ChannelArchiverDataServerPVInfo> alreadyExistingServers, ChannelArchiverDataServerInfo serverInfo, NamesHandler.ChannelDescription pvChannelDesc) { List<ChannelArchiverDataServerPVInfo> copyOfAlreadyExistingServers = new LinkedList<ChannelArchiverDataServerPVInfo>(); for(ChannelArchiverDataServerPVInfo alreadyExistingServer : alreadyExistingServers) { if(alreadyExistingServer.getServerInfo().equals(serverInfo)) { logger.debug("Removing a channel archiver server that already exists " + alreadyExistingServer.toString()); } else { copyOfAlreadyExistingServers.add(alreadyExistingServer); } } int beforeCount = alreadyExistingServers.size(); alreadyExistingServers.clear(); alreadyExistingServers.addAll(copyOfAlreadyExistingServers); // Readd the CA server - this should take into account any updated start times, end times and so on. alreadyExistingServers.add(new ChannelArchiverDataServerPVInfo(serverInfo, pvChannelDesc.getStartSec(), pvChannelDesc.getEndSec())); int afterCount = alreadyExistingServers.size(); logger.debug("We had " + beforeCount + " and now we have " + afterCount + " when adding external ChannelArchiver server"); // Sort the servers by ascending time stamps before adding it back. ChannelArchiverDataServerPVInfo.sortServersBasedOnStartAndEndSecs(alreadyExistingServers); } @Override public List<ChannelArchiverDataServerPVInfo> getChannelArchiverDataServers(String pvName) { String normalizedPVName = PVNames.normalizePVName(pvName); logger.debug("Looking for CA sever for pv " + normalizedPVName); return pv2ChannelArchiverDataServer.get(normalizedPVName); } @Override public PolicyConfig computePolicyForPV(String pvName, MetaInfo metaInfo, UserSpecifiedSamplingParams userSpecParams) throws IOException { try(InputStream is = this.getPolicyText()) { logger.debug("Computing policy for pvName"); HashMap<String, Object> pvInfo = new HashMap<String, Object>(); pvInfo.put("dbrtype", metaInfo.getArchDBRTypes().toString()); pvInfo.put("elementCount", metaInfo.getCount()); pvInfo.put("eventRate", metaInfo.getEventRate()); pvInfo.put("storageRate", metaInfo.getStorageRate()); pvInfo.put("aliasName", metaInfo.getAliasName()); if(userSpecParams != null && userSpecParams.getPolicyName() != null) { logger.debug("Passing user override of policy " + userSpecParams.getPolicyName() + " as the dict entry policyName"); pvInfo.put("policyName", userSpecParams.getPolicyName()); } if(userSpecParams.getControllingPV() != null) { pvInfo.put("controlPV", userSpecParams.getControllingPV()); } HashMap<String,String> otherMetaInfo = metaInfo.getOtherMetaInfo(); for(String otherMetaInfoKey : this.getExtraFields()) { if(otherMetaInfo.containsKey(otherMetaInfoKey)) { if(otherMetaInfoKey.equals("ADEL") || otherMetaInfoKey.equals("MDEL")) { try { pvInfo.put(otherMetaInfoKey, Double.parseDouble(otherMetaInfo.get(otherMetaInfoKey))); } catch(Exception ex) { logger.error("Exception adding MDEL and ADEL to the info", ex); } } else { pvInfo.put(otherMetaInfoKey,otherMetaInfo.get(otherMetaInfoKey)); } } } if(logger.isDebugEnabled()) { StringBuilder buf = new StringBuilder(); buf.append("Before computing policy for"); buf.append(pvName); buf.append(" pvInfo is \n"); for(String key : pvInfo.keySet()) { buf.append(key); buf.append("="); buf.append(pvInfo.get(key)); buf.append("\n"); } logger.debug(buf.toString()); } try { // We only have one policy in the cache... ExecutePolicy executePolicy = theExecutionPolicy.get("ThePolicy"); PolicyConfig policyConfig = executePolicy.computePolicyForPV(pvName, pvInfo); return policyConfig; } catch (ExecutionException e) { Throwable cause = e.getCause(); logger.error("Exception executing policy for pv " + pvName, cause); if(cause instanceof IOException) { throw (IOException) cause; } else { throw new IOException(cause); } } } } @Override public HashMap<String, String> getPoliciesInInstallation() throws IOException { try(ExecutePolicy executePolicy = new ExecutePolicy(this)) { return executePolicy.getPolicyList(); } } @Override public List<String> getFieldsArchivedAsPartOfStream() throws IOException { try(ExecutePolicy executePolicy = new ExecutePolicy(this)) { return executePolicy.getFieldsArchivedAsPartOfStream(); } } @Override public TypeSystem getArchiverTypeSystem() { return new PBTypeSystem(); } private boolean finishedLoggingPolicyLocation = false; @Override public InputStream getPolicyText() throws IOException { String policiesPyFile = System.getProperty(ARCHAPPL_POLICIES); if(policiesPyFile == null) { policiesPyFile = System.getenv(ARCHAPPL_POLICIES); if(policiesPyFile != null) { if(!finishedLoggingPolicyLocation) { configlogger.info("Obtained policies location from environment " + policiesPyFile); finishedLoggingPolicyLocation = true; } return new FileInputStream(new File(policiesPyFile)); } else { logger.info("Looking for /WEB-INF/classes/policies.py in classpath"); if(servletContext != null) { if(!finishedLoggingPolicyLocation) { configlogger.info("Using policies file /WEB-INF/classes/policies.py found in classpath"); finishedLoggingPolicyLocation = true; } return servletContext.getResourceAsStream("/WEB-INF/classes/policies.py"); } else { throw new IOException("Cannot determine location of policies file as both servlet context and webInfClassesFolder are null"); } } } else { if(!finishedLoggingPolicyLocation) { configlogger.info("Obtained policies location from system property " + policiesPyFile); finishedLoggingPolicyLocation = true; } return new FileInputStream(new File(policiesPyFile)); } } @Override public EngineContext getEngineContext() { return engineContext; } @Override public MgmtRuntimeState getMgmtRuntimeState() { return mgmtRuntime; } @Override public WAR_FILE getWarFile() { return warFile; } /** * Return a string representation of the member. * @param member * @return */ private String getMemberKey(Member member) { // We use deprecated versions of the methods as the non-deprecated versions do not work as of 2.0x? return member.getSocketAddress().toString(); } @Override public EventBus getEventBus() { return eventBus; } @Override public PVNameToKeyMapping getPVNameToKeyConverter() { return pvName2KeyConverter; } /** * Load typeInfos into the cluster hashmaps from the persistence layer on startup. * To avoid overwhelming the cluster, we batch the loads */ private void loadTypeInfosFromPersistence() { try { configlogger.info("Loading PVTypeInfo from persistence"); List<String> upgradedPVs = new LinkedList<String>(); List<String> pvNamesFromPersistence = persistanceLayer.getTypeInfoKeys(); HashMap<String, PVTypeInfo> newTypeInfos = new HashMap<String, PVTypeInfo>(); HashMap<String, ApplianceInfo> newPVMappings = new HashMap<String, ApplianceInfo>(); int objectCount = 0; int batch = 0; int clusterPVCount = 0; for(String pvNameFromPersistence : pvNamesFromPersistence) { PVTypeInfo typeInfo = persistanceLayer.getTypeInfo(pvNameFromPersistence); if(typeInfo.getApplianceIdentity().equals(myIdentity)) { // Here's where we put schema update logic upgradeTypeInfo(typeInfo, upgradedPVs); String pvName = typeInfo.getPvName(); newTypeInfos.put(pvName, typeInfo); newPVMappings.put(pvName, appliances.get(typeInfo.getApplianceIdentity())); pvsForThisAppliance.add(pvName); if(typeInfo.isPaused()) { pausedPVsForThisAppliance.add(pvName); } String[] parts = this.pvName2KeyConverter.breakIntoParts(pvName); for(String part : parts) { if(!parts2PVNamesForThisAppliance.containsKey(part)) { parts2PVNamesForThisAppliance.put(part, new ConcurrentSkipListSet<String>()); } parts2PVNamesForThisAppliance.get(part).add(pvName); } objectCount++; } // Add in batch sizes of 1000 or so... if(objectCount > 1000) { this.typeInfos.putAll(newTypeInfos); this.pv2appliancemapping.putAll(newPVMappings); for(String pvName : newTypeInfos.keySet()) { applianceAggregateInfo.addInfoForPV(pvName, newTypeInfos.get(pvName), this); } clusterPVCount += newTypeInfos.size(); newTypeInfos = new HashMap<String, PVTypeInfo>(); newPVMappings = new HashMap<String, ApplianceInfo>(); objectCount = 0; logger.debug("Adding next batch of PVs " + batch++); } } if(newTypeInfos.size() > 0) { logger.debug("Adding final batch of PVs from persistence"); this.typeInfos.putAll(newTypeInfos); this.pv2appliancemapping.putAll(newPVMappings); for(String pvName : newTypeInfos.keySet()) { applianceAggregateInfo.addInfoForPV(pvName, newTypeInfos.get(pvName), this); } clusterPVCount += newTypeInfos.size(); } configlogger.info("Done loading " + + clusterPVCount + " PVs from persistence into cluster"); for(String upgradedPVName : upgradedPVs) { logger.debug("PV " + upgradedPVName + "'s schema was upgraded"); persistanceLayer.putTypeInfo(upgradedPVName, getTypeInfoForPV(upgradedPVName)); logger.debug("Done persisting upgraded PV's " + upgradedPVName + "'s typeInfo"); } } catch(Exception ex) { configlogger.error("Exception loading PVs from persistence", ex); } } /** * The occasional upgrade to PVTypeInfo schema is handed here. * @param typeInfo - Typeinfo to be upgraded * @param upgradedPVs - Add the pvName here if we actually did an upgrade to the typeInfo. */ private void upgradeTypeInfo(PVTypeInfo typeInfo, List<String> upgradedPVs) { // We added the chunkKey to typeInfo to permanently remember the key mapping to accomodate slowly changing key mappings. // This could be a possibility after talking to SPEAR folks. if(!typeInfo.keyAlreadyGenerated()) { typeInfo.setChunkKey(this.pvName2KeyConverter.convertPVNameToKey(typeInfo.getPvName())); upgradedPVs.add(typeInfo.getPvName()); } } /** * Load alias mappings from persistence on startup in batches */ private void loadAliasesFromPersistence() { try { configlogger.info("Loading aliases from persistence"); List<String> pvNamesFromPersistence = persistanceLayer.getAliasNamesToRealNamesKeys(); HashMap<String, String> newAliases = new HashMap<String, String>(); int objectCount = 0; int batch = 0; int clusterPVCount = 0; for(String pvNameFromPersistence : pvNamesFromPersistence) { String realName = persistanceLayer.getAliasNamesToRealName(pvNameFromPersistence); if(this.pvsForThisAppliance.contains(realName)) { newAliases.put(pvNameFromPersistence, realName); // Add the alias into the trie String[] parts = this.pvName2KeyConverter.breakIntoParts(pvNameFromPersistence); for(String part : parts) { if(!parts2PVNamesForThisAppliance.containsKey(part)) { parts2PVNamesForThisAppliance.put(part, new ConcurrentSkipListSet<String>()); } parts2PVNamesForThisAppliance.get(part).add(pvNameFromPersistence); } } // Add in batch sizes of 1000 or so... if(objectCount > 1000) { this.aliasNamesToRealNames.putAll(newAliases); clusterPVCount += newAliases.size(); newAliases = new HashMap<String, String>(); objectCount = 0; logger.debug("Adding next batch of aliases " + batch++); } } if(newAliases.size() > 0) { logger.debug("Adding final batch of aliases from persistence"); this.aliasNamesToRealNames.putAll(newAliases); clusterPVCount += newAliases.size(); } configlogger.info("Done loading " + clusterPVCount + " aliases from persistence into cluster "); } catch(Exception ex) { configlogger.error("Exception loading aliases from persistence", ex); } } /** * Load any pending archive requests that have not been fulfilled yet on startup * Also, start their workflows.. * @throws ConfigException */ private void loadArchiveRequestsFromPersistence() throws ConfigException { try { configlogger.info("Loading archive requests from persistence"); List<String> pvNamesFromPersistence = persistanceLayer.getArchivePVRequestsKeys(); HashMap<String, UserSpecifiedSamplingParams> newArchiveRequests = new HashMap<String, UserSpecifiedSamplingParams>(); int objectCount = 0; int batch = 0; int clusterPVCount = 0; for(String pvNameFromPersistence : pvNamesFromPersistence) { UserSpecifiedSamplingParams userSpecifiedParams = persistanceLayer.getArchivePVRequest(pvNameFromPersistence); // We should not need to add an appliance check here.. However, if after production deployment, we determine we need to do so; this is the right place. newArchiveRequests.put(pvNameFromPersistence, userSpecifiedParams); // Add in batch sizes of 1000 or so... if(objectCount > 1000) { this.archivePVRequests.putAll(newArchiveRequests); clusterPVCount += newArchiveRequests.size(); newArchiveRequests = new HashMap<String, UserSpecifiedSamplingParams>(); objectCount = 0; logger.debug("Adding next batch of archive pv requests " + batch++); } } if(newArchiveRequests.size() > 0) { logger.debug("Adding final batch of archive pv requests from persistence"); this.archivePVRequests.putAll(newArchiveRequests); clusterPVCount += newArchiveRequests.size(); } configlogger.info("Done loading " + clusterPVCount + " archive pv requests from persistence into cluster "); } catch(Exception ex) { configlogger.error("Exception loading archive pv requests from persistence", ex); } } /** * Initialize the persistenceLayer using environment/system property. * By default, initialize the MySQLPersistence * @throws ConfigException */ private void initializePersistenceLayer() throws ConfigException { String persistenceFromEnv = System.getenv(ARCHAPPL_PERSISTENCE_LAYER); if(persistenceFromEnv == null || persistenceFromEnv.equals("")) { persistenceFromEnv = System.getProperty(ARCHAPPL_PERSISTENCE_LAYER); } if(persistenceFromEnv == null || persistenceFromEnv.equals("")) { logger.info("Using MYSQL for persistence; we expect to find a JNDI connection pool called jdbc/archappl"); persistanceLayer = new MySQLPersistence(); } else { try { logger.info("Using persistence provided by class " + persistenceFromEnv); persistanceLayer = (ConfigPersistence) getClass().getClassLoader().loadClass(persistenceFromEnv).newInstance(); } catch(Exception ex) { throw new ConfigException("Exception initializing persistence layer using " + persistenceFromEnv, ex); } } } public ProcessMetrics getProcessMetrics() { return processMetrics; } @Override public String getWebInfFolder() { return servletContext.getRealPath("WEB-INF/"); } private void loadExternalServersFromPersistence() throws ConfigException { try { configlogger.info("Loading external servers from persistence"); List<String> externalServerKeys = persistanceLayer.getExternalDataServersKeys(); for(String serverUrl : externalServerKeys) { String archivesCSV = persistanceLayer.getExternalDataServer(serverUrl); if(this.getExternalArchiverDataServers().containsKey(serverUrl)) { configlogger.info("Skipping adding " + serverUrl + " on this appliance as another appliance has already added it"); } else { this.getExternalArchiverDataServers().put(serverUrl, archivesCSV); String[] archives = archivesCSV.split(","); this.startupExecutor.schedule(new Runnable() { @Override public void run() { try { for(int i = 0; i < archives.length; i++) { String archive = archives[i]; loadExternalArchiverPVs(serverUrl, archive); } } catch(Exception ex) { logger.error("Exception adding Channel Archiver archives " + serverUrl + " - " + archivesCSV, ex); } } }, 15, TimeUnit.SECONDS); } } configlogger.info("Done loading external servers from persistence "); } catch(Exception ex) { configlogger.error("Exception loading external servers from persistence", ex); } } private void registerForNewExternalServers(IMap<Object, Object> dataServerMap) throws ConfigException { dataServerMap.addEntryListener(new EntryAddedListener<Object, Object>() { @Override public void entryAdded(EntryEvent<Object, Object> arg0) { String url = (String) arg0.getKey(); String archivesCSV = (String) arg0.getValue(); try { addExternalArchiverDataServer(url, archivesCSV); } catch(Exception ex) { logger.error("Exception syncing external data server " + url + archivesCSV, ex); } } }, true); dataServerMap.addEntryListener(new EntryRemovedListener<Object, Object>() { @Override public void entryRemoved(EntryEvent<Object, Object> arg0) { String url = (String) arg0.getKey(); String archivesCSV = (String) arg0.getValue(); try { removeExternalArchiverDataServer(url, archivesCSV); } catch(Exception ex) { logger.error("Exception syncing external data server " + url + archivesCSV, ex); } } }, true); } @Override public Set<String> getPausedPVsInThisAppliance() { if(pausedPVsForThisAppliance != null) { logger.debug("Returning the locally cached copy of the paused pvs for this appliance"); return pausedPVsForThisAppliance; } else { logger.debug("Fetching the list of paused PVs for this appliance from the mgmt app"); JSONArray pvs = GetUrlContent.getURLContentAsJSONArray(myApplianceInfo.getMgmtURL() + "/getPausedPVsForThisAppliance"); HashSet<String> retval = new HashSet<String>(); for(Object pv : pvs) { retval.add((String) pv); } return retval; } } @Override public void refreshPVDataFromChannelArchiverDataServers() { Map<String, String> existingCAServers = this.getExternalArchiverDataServers(); for(String serverURL : existingCAServers.keySet()) { String archivesCSV = existingCAServers.get(serverURL); String[] archives = archivesCSV.split(","); try { for(int i = 0; i < archives.length; i++) { String archive = archives[i]; loadExternalArchiverPVs(serverURL, archive); } } catch(Throwable ex) { logger.error("Exception adding Channel Archiver archives " + serverURL + " - " + archivesCSV, ex); } } } @Override public boolean getNamedFlag(String name) { if(name == null) { return false; } if(name.equalsIgnoreCase("false")) { return false; } if(name.equalsIgnoreCase("true")) { return true; } if(namedFlags.containsKey(name)) { return namedFlags.get(name); } // If we don't know about this named flag, we return false; return false; } @Override public void setNamedFlag(String name, boolean value) { namedFlags.put(name, value); } @Override public Set<String> getNamedFlagNames() { return namedFlags.keySet(); } }