/******************************************************************************* * Copyright (c) 2010 Oak Ridge National Laboratory. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html ******************************************************************************/ package org.epics.archiverappliance.engine.pv; import gov.aps.jca.CAException; import gov.aps.jca.Channel; import gov.aps.jca.Context; import gov.aps.jca.event.ConnectionListener; import java.util.HashMap; import org.apache.log4j.Logger; import org.epics.archiverappliance.config.ConfigService; /** * Handle PV context, pool PVs by name. * * When using the pure java CA client implementation, it returns the same * 'channel' when trying to access the same PV name multiple times. That's good, * but I don't know how to determine if the channel for this EPICS_V3_PV is * actually shared. Calling destroy() on such a shared channel creates problems.<br> * The PVContext adds its own hash map of channels and keeps a reference count. * * @author Kay Kasemir * s@version Initial version:CSS * @version 4-Jun-2012, Luofeng Li:added codes to support for the new archiver */ @SuppressWarnings("nls") public class PVContext { private static Logger logger = Logger.getLogger(PVContext.class.getName()); public enum MonitorMask { /** Listen to changes in value beyond 'MDEL' threshold or alarm state */ VALUE(1 | 4), /** Listen to changes in value beyond 'ADEL' archive limit */ ARCHIVE(2), /** Listen to changes in alarm state */ ALARM(4); final private int mask; private MonitorMask(final int mask) { this.mask = mask; } /** @return Mask bits used in underlying CA call */ public int getMask() { return mask; } } /** * In principle, we like to close the context when it is no longer needed. * * This is in fact required for CAJ to close all threads. * * For JCA, however, there are problems: With R3.14.8.2 on Linux there were * errors "pthread_create error Invalid argument". With R3.14.11 on OS X * 10.5.8 the call to jca_context.destroy() caused an * "Invalid memory access ..." crash. * * -> We keep the context open. */ /** * Set to <code>true</code> if the pure Java CA context should be used. * <p> * Changes only have an effect before the very first channel is created. */ /** The JCA context reference count. */ static private long jca_refs = 0; /** map of channels. */ static private HashMap<String, RefCountedChannel> channels = new HashMap<String, RefCountedChannel>(); private static ConfigService configservice; public static void setConfigservice(ConfigService configservice) { PVContext.configservice = configservice; } /** Initialize the JA library, start the command thread. */ static void initJCA() throws Exception { ++jca_refs; } /** * Disconnect from the JA library. * <p> * Without this step, JCA threads can stay around and prevent the * application from quitting. */ private static void exitJCA() { --jca_refs; } /** * Get a new channel, or a reference to an existing one. * * @param name * Channel name * @param jcaCommandThreadId The JCA Command thread. * @param conn_callback   * @return reference to channel * @throws Exception * on error * @see #releaseChannel */ public synchronized static RefCountedChannel getChannel(final String name, int jcaCommandThreadId, final ConnectionListener conn_callback) throws Exception { initJCA(); RefCountedChannel channel_ref = channels.get(name); if (channel_ref == null) { final Channel channel = configservice.getEngineContext() .getJCACommandThread(jcaCommandThreadId).getContext() .createChannel(name, conn_callback); if (channel == null) throw new Exception("Cannot create channel '" + name + "'"); channel_ref = new RefCountedChannel(channel); channels.put(name, channel_ref); } else { channel_ref.incRefs(); // // Must have been getChannel() == null, but how is that possible? channel_ref.getChannel().addConnectionListener(conn_callback); } return channel_ref; } /** * Release a channel. * * @param channel_ref * Channel to release. * @throws CAException * @throws IllegalStateException * @see #getChannel(String) */ synchronized static void releaseChannel( final RefCountedChannel channel_ref, final ConnectionListener conn_callback) throws IllegalStateException, CAException { final String channelname = channel_ref.getChannel().getName(); try { channel_ref.getChannel().removeConnectionListener(conn_callback); } catch(IllegalStateException ex) { // You'd get this if the channel is already closed logger.debug("Exception removing connection listener from channel " + channelname, ex); } try { if (channel_ref.decRefs() <= 0) { channels.remove(channelname); channel_ref.dispose(); } } catch(IllegalStateException ex) { // You'd get this if the channel is already closed logger.debug("Exception disposing channel channel " + channelname, ex); } exitJCA(); } /** * Add a command to the JCACommandThread. * @param pvName The name of the PV that this applies to * @param jcaCommandThreadId The JCA Command thread for this PV. * @param channel_ref this can be null * @param msg   * @param command The runnable that will run in the specified command thread */ public static void scheduleCommand(String pvName, int jcaCommandThreadId, RefCountedChannel channel_ref, String msg, final Runnable command) { try { if(channel_ref != null && channel_ref.getChannel() != null) { Context context = channel_ref.getChannel().getContext(); if(!configservice.getEngineContext().doesContextMatchThread(context, jcaCommandThreadId)) { logger.error("Command for pv " + pvName + " is incorrectly scheduled on thread " + jcaCommandThreadId + " in " + msg); // try { // throw new Exception(); // } catch (Exception ex) { // logger.error("Command for pv " + pvName + " is incorrectly scheduled on thread " + jcaCommandThreadId, ex); // } } } } catch(Throwable t) { logger.error("Exception scheduling command for pv " + pvName, t); } configservice.getEngineContext().getJCACommandThread(jcaCommandThreadId).addCommand(command); } /** * Helper for unit test. * * @return <code>true</code> if all has been release. */ static boolean allReleased() { return jca_refs == 0; } /** * Return the channel count as this class sees it. * @return int channels size */ public static int getChannelCount() { return channels.size(); } }