/*******************************************************************************
* ALMA - Atacama Large Millimeter Array
* Copyright (c) ESO - European Southern Observatory, 2011
* (in the framework of the ALMA collaboration).
* All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*******************************************************************************/
package alma.acs.nc.testsupport;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Logger;
import alma.ACSErrTypeCommon.wrappers.AcsJIllegalStateEventEx;
import alma.acs.container.ContainerServicesBase;
import alma.acs.exceptions.AcsJException;
import alma.acs.nc.AcsEventPublisher;
import alma.acs.nc.AcsEventSubscriber;
import alma.acsnc.EventDescription;
/**
* This class provides a simple in-memory notification mechanism
* that tries to mimic a corba notification channel,
* without the need for ACS services to run.
* <p>
* <b>It should be used only to simplify unit test setups, and never in an operational environment.</b>
* <p>
* Unit tests may use this class in two ways:
* <ul>
* <li>Explicitly: The test can instantiate InMemoryNcFake and create the publisher
* and subscriber objects from there. This will work fine if the classes under test are designed
* to get the supplier/subscriber objects injected, rather than retrieving them directly
* from the container services.
* <li>Implicitly: The test can wrap the container services (e.g. by extending jcont :: ContainerServicesProxy)
* so that methods {@link ContainerServicesBase#createNotificationChannelPublisher(String, Class)}
* and {@link ContainerServicesBase#createNotificationChannelSubscriber(String, Class)}
* are overridden and use this InMemoryNcFake class. This should work also for cases where the container services
* get passed around the code, and deep down in some class under test the suppliers / subscribers are created.
* </ul>
* In either case, the resulting supplier and subscriber objects will have the same interfaces as their real NC variants.
* See also http://jira.alma.cl/browse/COMP-2890 about such tests.
*/
public class InMemoryNcFake
{
private final String channelName;
private final Logger logger;
private final ContainerServicesBase services;
/**
* Not clear yet if it makes sense to keep track of the connected publishers.
* It may be interesting for some tests to get a list of publishers and subscribers
* in the end to check if some were not disconnected.
* At the moment we just keep track of the publishers without further using this information.
*/
private final List<InMemoryPublisher<?>> publishers;
private final List<InMemorySubscriber<?>> subscribers;
/**
* Lets multiple supplier threads send data concurrently to the subscribers
* but protects them from changes in the {@link #subscribers} list.
*/
private final ReentrantReadWriteLock subscribersLock;
/**
* @param services
* @param channelName
*/
public InMemoryNcFake(ContainerServicesBase services, String channelName) {
this.channelName = channelName;
this.services = services;
this.logger = services.getLogger();
this.publishers = new ArrayList<InMemoryPublisher<?>>();
this.subscribers = new ArrayList<InMemorySubscriber<?>>();
subscribersLock = new ReentrantReadWriteLock(true);
}
/**
* Factory method for publishers.
*/
public synchronized <T> AcsEventPublisher<T> createPublisher(String publisherName, Class<T> eventType) {
InMemoryPublisher<T> ret = new InMemoryPublisher<T>(this, publisherName, logger);
publishers.add(ret);
return ret;
}
/**
* Factory method for subscribers.
*/
public <T> AcsEventSubscriber<T> createSubscriber(String subscriberName, Class<T> eventType) throws AcsJException {
InMemorySubscriber<T> ret = new InMemorySubscriber<T>(this, services, subscriberName, eventType);
subscribersLock.writeLock().lock();
try {
subscribers.add(ret);
} finally {
subscribersLock.writeLock().unlock();
}
return ret;
}
/**
* Called by {@link InMemoryPublisher#disconnect()}
* when the user disconnects the publisher.
*/
void disconnectPublisher(InMemoryPublisher<?> publisher) {
boolean done = publishers.remove(publisher);
if (done) {
logger.finer("Disconnected publisher " + publisher.publisherName);
}
else {
logger.warning("Failed to disconnect publisher " + publisher.publisherName);
}
}
/**
* Called by {@link InMemorySubscriber#destroyConnectionAction}
* when the user disconnects the subscriber.
*/
void disconnectSubscriber(InMemorySubscriber<?> subscriber) {
subscribersLock.writeLock().lock();
try {
boolean done = subscribers.remove(subscriber);
if (done) {
logger.finer("Disconnected subscriber " + subscriber.getClientName());
}
else {
logger.warning("Failed to disconnect subscriber " + subscriber.getClientName());
}
} finally {
subscribersLock.writeLock().unlock();
}
}
/**
* Called by the publishers to deliver their data.
* <p>
* This method dispatches (synchronously in the calling thread) the data to all subscribers.
* This is OK because the subscribers implement buffers, so that even with slow subscribers
* this call should return fast.
* We do not filter by event type here because the in-memory subscriber is in charge of this.
* The subscriber may need all events if it has a generic subscription.
* <p>
* This method is thread-safe.
* @param data
* @param desc
*/
void pushData(Object data, EventDescription desc) {
//
subscribersLock.readLock().lock();
try {
for (InMemorySubscriber<?> subscriber : subscribers) {
try {
subscriber.pushData(data, desc);
} catch (AcsJIllegalStateEventEx ex) {
// Could legally happen if startReceivingEvents was never called on the subscriber.
}
}
}
finally {
subscribersLock.readLock().unlock();
}
}
/**
* hashCode() based on {{@link #channelName}.
*/
@Override
public int hashCode() {
return channelName.hashCode();
}
/**
* equals based on {{@link #channelName}.
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (!(obj instanceof InMemoryNcFake))
return false;
InMemoryNcFake other = (InMemoryNcFake) obj;
if (!channelName.equals(other.channelName))
return false;
return true;
}
}