/*
* Copyright 2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.rioproject.impl.fdh;
import net.jini.config.Configuration;
import net.jini.core.lookup.ServiceID;
import net.jini.core.lookup.ServiceItem;
import net.jini.lookup.LookupCache;
import net.jini.lookup.ServiceDiscoveryEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static org.rioproject.impl.fdh.Options.*;
/**
* The AbstractFaultDetectionHandler provides a base class which can be extended
* to provide concrete FaultDetectionHandler capabilities. The basic infrastructure
* included in the AbstractFaultDetectionHandler includes
* {@link FaultDetectionListener} registration and notification,
* a {@link net.jini.lookup.ServiceDiscoveryListener} implementation
*
* Properties that will be common across all classes which extend this class are also
* provided:
* <ul>
* <li>retryCount
* <li>retryTimeout
* </ul>
*
* @see FaultDetectionHandler
* @see FaultDetectionListener
*
* @author Dennis Reedy
*/
@SuppressWarnings("PMD.AvoidThrowingRawExceptionTypes")
public abstract class AbstractFaultDetectionHandler implements FaultDetectionHandler<ServiceID> {
private static final int DEFAULT_RETRY_COUNT = 3;
private static final long DEFAULT_RETRY_TIMEOUT = 1000;
private static final long DEFAULT_INVOCATION_DELAY = TimeUnit.SECONDS.toMillis(1);
/** Object that can be used to communicate to the service */
protected Object proxy;
long invocationDelay = DEFAULT_INVOCATION_DELAY;
int retryCount = DEFAULT_RETRY_COUNT;
long retryTimeout = DEFAULT_RETRY_TIMEOUT;
/** Collection of FaultDetectionListeners */
private final Set<FaultDetectionListener<ServiceID>> listeners = new HashSet<>();
/** ServiceID used to discover service instance */
private ServiceID serviceID;
/** The LookupCache */
private LookupCache lCache;
/** Flag to indicate the utility is terminating */
protected boolean terminating = false;
/** Configuration arguments */
protected String[] configArgs;
/** A Configuration object */
protected Configuration config;
/** Listener for the LookupCache */
private ServiceCacheListener fdhListener;
/** Class which provides service monitoring */
ServiceMonitor serviceMonitor;
/** A Logger */
private static Logger logger = LoggerFactory.getLogger(AbstractFaultDetectionHandler.class);
@Override public void configure(Properties properties) {
if(properties.getProperty(INVOCATION_DELAY)!=null) {
invocationDelay = TimeUnit.SECONDS.toMillis(Long.parseLong(properties.getProperty(INVOCATION_DELAY)));
}
if(properties.getProperty(RETRY_COUNT)!=null) {
retryCount = Integer.parseInt(properties.getProperty(RETRY_COUNT));
}
if(properties.getProperty(RETRY_TIMEOUT)!=null) {
retryTimeout = TimeUnit.SECONDS.toMillis(Long.parseLong(properties.getProperty(RETRY_TIMEOUT)));
}
}
/**
* @see FaultDetectionHandler#register
*/
public void register(FaultDetectionListener<ServiceID> listener) {
if(listener!=null) {
synchronized (listeners) {
listeners.add(listener);
}
}
}
public LookupCache getLookupCache() {
return lCache;
}
/**
* @see FaultDetectionHandler#unregister
*/
public void unregister(FaultDetectionListener<ServiceID> listener) {
if(listener!=null) {
synchronized (listeners) {
listeners.remove(listener);
}
}
}
@Override public void setLookupCache(LookupCache lCache) {
this.lCache = lCache;
if(this.lCache != null) {
fdhListener = getServiceCacheListener();
try {
this.lCache.addListener(fdhListener);
} catch(IllegalStateException e) {
logger.error("Unable to set the FaultDetectionListener", e);
}
}
}
/**
* @see FaultDetectionHandler#monitor
*/
public void monitor(Object proxy, ServiceID id) {
if(proxy == null)
throw new IllegalArgumentException("proxy is null");
if(id == null)
throw new IllegalArgumentException("id is null");
this.proxy = proxy;
this.serviceID = id;
serviceMonitor = getServiceMonitor();
}
/**
* Get the class which implements the ServiceMonitor
*
* @return A ServiceMonitor
*
* @throws Exception if the ServiceMonitor cannot be created
*/
protected abstract ServiceMonitor getServiceMonitor();
protected ServiceCacheListener getServiceCacheListener() {
return new FDHListener();
}
public long getInvocationDelay() {
return invocationDelay;
}
public long getRetryTimeout() {
return retryTimeout;
}
public int getRetryCount() {
return retryCount;
}
/**
* @see FaultDetectionHandler#terminate
*/
public void terminate() {
if(terminating)
return;
terminating = true;
if(lCache != null && fdhListener!=null) {
try {
lCache.removeListener(fdhListener);
} catch (Throwable t) {
logger.warn("Exception {} removing Listener from LookupCache", t.getClass().getName());
}
}
if(serviceMonitor != null) {
serviceMonitor.drop();
}
listeners.clear();
}
/**
* Notify FaultDetectionListener instances the service has been removed
*/
@SuppressWarnings("unchecked")
protected void notifyListeners() {
FaultDetectionListener<ServiceID>[] ls;
synchronized(listeners) {
ls = listeners.toArray(new FaultDetectionListener[listeners.size()]);
}
for(FaultDetectionListener<ServiceID> l : ls) {
l.serviceFailure(proxy, serviceID);
}
}
protected Set<FaultDetectionListener<ServiceID>> getListeners() {
return listeners;
}
public void setRetryCount(int retryCount) {
this.retryCount = retryCount;
}
public void setRetryTimeout(long retryTimeout) {
this.retryTimeout = retryTimeout;
}
/**
* Defines the semantics of an internal class which will be used in perform
* service-specific monitoring
*/
public interface ServiceMonitor {
/**
* Drop the Monitor, its no longer needed
*/
void drop();
/**
* Verify that the service can be reached. If the service cannot be reached
* return false
*
* @return true or false
*/
boolean verify();
}
/**
* Listen for service removal events for the service we're interested in
*/
private class FDHListener extends ServiceCacheListener {
boolean reachable = false;
/**
* Notification that the service has been removed
*
* @param sdEvent The ServiceDiscoveryEvent
*/
public void serviceRemoved(ServiceDiscoveryEvent sdEvent) {
ServiceItem item = sdEvent.getPreEventServiceItem();
if(!item.serviceID.equals(serviceID)) {
return;
}
if(logger.isTraceEnabled())
logger.trace("FDH: service [{}], removed notification", NameHelper.getName(item.attributeSets));
if(serviceMonitor != null) {
for(int i = 0; i < retryCount; i++) {
reachable = serviceMonitor.verify();
if(!reachable) {
break;
}
if(retryTimeout > 0) {
try {
Thread.sleep(retryTimeout);
} catch(InterruptedException ie) {
// Restore the interrupted status
Thread.currentThread().interrupt();
if(logger.isTraceEnabled())
logger.trace("FDH: service [{}], proxy [{}] interrupted during retry timeout",
NameHelper.getName(item.attributeSets), proxy.getClass().getName());
}
}
}
}
if(logger.isTraceEnabled())
logger.trace("FDH: service [{}], proxy [{}] is reachable [{}]",
NameHelper.getName(item.attributeSets), proxy.getClass().getName(), reachable);
if(!reachable && !terminating) {
notifyListeners();
terminate();
}
}
@Override public void terminate() {
}
}
}