/*
* Copyright 2008 the original author or authors.
* Copyright 2005 Sun Microsystems, Inc.
*
* 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.client;
import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.config.EmptyConfiguration;
import net.jini.core.discovery.LookupLocator;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryManagement;
import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.LookupDiscoveryManager;
import org.rioproject.config.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* A strategy has been taken in an attempt to conserve resources in a multi-service
* JVM environment as it relates to DiscoveryManagement. The DiscoveryManagementPool
* class provides an approach that enables a caller to get an existing
* DiscoveryManagement instance from a pool of previously created
* DiscoveryManagement instances, instead of creating a new DiscoveryManagement
* instance, and going through the overhead of DiscoveryManagement creation and
* performing discovery each time a service is initialized.
* <p>
* The criteria for pooling of created DiscoveryManagement instances is to match
* groups, locators and a shared name. The default behavior for ServiceBeans is to
* use the OperationalString name as the shared name. Therefore all services
* instantiated in the same Cybernode, that are in the same OperationalString,
* which have the same DiscoveryManagement groups and locators, will share the
* same DiscoveryManagement instance.
* <p>
* Of course use of shared DiscoveryManagement instances must be used with care
* as changing the settings of a shared DiscoveryManagement instance may present
* problems for other users of that DiscoveryManagement instance.
* <p>
* As a deployer, if you want DiscoveryManagement sharing turned off for services,
* declaring the following will turn it off:
* <br><br>
* <tt>
* <ServiceBean Name="Does not share well" DiscoveryManagementPooling="no">
* </tt>
*
* @author Dennis Reedy
*/
public class DiscoveryManagementPool {
private final List<DiscoveryControl> pool = new ArrayList<DiscoveryControl>();
private final static String COMPONENT = DiscoveryManagementPool.class.getName();
private static Logger logger = LoggerFactory.getLogger(COMPONENT);
private Configuration defaultConfig;
private static DiscoveryManagementPool singleton = new DiscoveryManagementPool();
private DiscoveryManagementPool() {
if(logger.isDebugEnabled())
logger.debug("Create new DiscoveryManagementPool");
}
/**
* Get the singleton instance of the DiscoveryManagementPool
*
* @return The singleton DiscoveryManagementPool instance
*/
public static synchronized DiscoveryManagementPool getInstance() {
return(singleton);
}
/**
* Set the Configuration property
*
* @param configuration The Configuration to use when creating
* DiscoveryManagementPool instances
*/
public void setConfiguration(Configuration configuration) {
defaultConfig = configuration;
if(logger.isDebugEnabled()) {
if(defaultConfig==null)
logger.debug("Set null configuration for DiscoveryManagementPool");
else
logger.debug("Set configuration for DiscoveryManagementPool {}", defaultConfig.toString());
}
}
/**
* This method will return an instance of <code>DiscoveryManagement</code>
* based on matching the shared name, groups and locators as criteria. If there
* is an existing instance of <code>DiscoveryManagement</code> instantiated by
* this utility, that instance will be returned. Otherwise a new
* <code>DiscoveryManagement</code> instance will be created and returned
*
* <p>Note: Use of returned DiscoveryManagement
* instances must be used with care as changing the settings of the returned
* DiscoveryManagement instance may present problems for other users of
* DiscoveryManagement instance and is not advised.
*
* @param sharedName The name the DiscoveryManagement instances are shared across
*
* @return A DiscoveryManagement object suitable for lookup discovery
* management using the following system properties:
* <ul>
* <li>org.rioproject.groups: a comma separated list of groups to use. If this
* property is not found LookupDiscoveryGroups will be set to
* LookupDiscovery.NO_GROUPS. Additionally, the value of "all" will be set
* to LookupDiscovery.ALL_GROUPS
* <li>org.rioproject.locators: a comma separated list of
* LookupLocator formatted URLs
*
* @throws IOException If the DiscoveryManagement instance cannot be created
*/
public DiscoveryManagement getDiscoveryManager(String sharedName) throws IOException {
String[] groups = JiniClient.parseGroups(System.getProperty(Constants.GROUPS_PROPERTY_NAME));
LookupLocator[] locators = JiniClient.parseLocators(System.getProperty(Constants.LOCATOR_PROPERTY_NAME));
return(getDiscoveryManager(sharedName, groups, locators, null, defaultConfig));
}
/**
* This method will return an instance of <code>DiscoveryManagement</code>
* based on matching the shared name, groups and locators as criteria. If there
* is an existing instance of <code>DiscoveryManagement</code> instantiated by
* this utility, that instance will be returned. Otherwise a new
* <code>DiscoveryManagement</code> instance will be created and returned
*
* <p>Note: Use of returned DiscoveryManagement
* instances must be used with care as changing the settings of the returned
* DiscoveryManagement instance may present problems for other users of
* DiscoveryManagement instance and is not advised.
*
* @param sharedName The name the DiscoveryManagement instances are shared across
* @param groups An array of String objects indicating the Jini Lookup
* Service groups to discover
* @param locators An array of LookupLocator objects indicating specific
* Jini Lookup Service instances to discover
* @return A DiscoveryManagement object suitable for lookup discovery
* management based on provided parameters
*
* @throws IOException If the DiscoveryManagement instance cannot be created
*/
public DiscoveryManagement getDiscoveryManager(String sharedName,
String[] groups,
LookupLocator[] locators) throws IOException {
return(getDiscoveryManager(sharedName, groups, locators, null, defaultConfig));
}
/**
* This method will return an instance of <code>DiscoveryManagement</code>
* based on matching the shared name, groups and locators as criteria. If there
* is an existing instance of <code>DiscoveryManagement</code> instantiated by
* this utility, that instance will be returned. Otherwise a new
* <code>DiscoveryManagement</code> instance will be created and returned
*
* <p>Note: Use of returned DiscoveryManagement
* instances must be used with care as changing the settings of the returned
* DiscoveryManagement instance may present problems for other users of
* DiscoveryManagement instance and is not advised.
*
* @param sharedName The name the DiscoveryManagement instances are shared across
* @param groups An array of String objects indicating the Jini Lookup Service
* groups to discover
* @param locators An array of LookupLocator objects indicating specific Jini
* Lookup Service instances to discover
* @param listener A instance of a DiscoveryListener which will be notified
* when targeted Jini Lookup Services are discovered or discarded. If this
* parameter is not null, then it will be added to either the created or existing
* <code>DiscoveryManagement</code> instance
* @param config If a DiscoveryManagement instance needs to be created, the
* Configuration to use when creating the DiscoveryManagement instance
*
* @return A DiscoveryManagement object suitable for lookup
* discovery management based on provided parameters
*
* @throws IOException If the DiscoveryManagement instance cannot be created
*/
public DiscoveryManagement getDiscoveryManager(String sharedName,
String[] groups,
LookupLocator[] locators,
DiscoveryListener listener,
Configuration config) throws IOException {
LookupDiscoveryManager ldm ;
synchronized(this) {
DiscoveryControl discoControl = getDiscoveryControl(sharedName);
if(discoControl==null) {
discoControl = new DiscoveryControl(sharedName);
pool.add(discoControl);
if(logger.isDebugEnabled())
logger.debug("Create new DiscoveryControl for [{}]", sharedName);
} else {
if(logger.isDebugEnabled())
logger.debug("DiscoveryControl obtained for [{}]", sharedName);
}
ldm = discoControl.getLookupDiscoveryManager(groups, locators);
if(ldm==null) {
ldm = discoControl.createLookupDiscoveryManager(groups, locators, listener, config);
} else {
((SharedDiscoveryManager)ldm).incrementRefCounter();
if(listener!=null)
ldm.addDiscoveryListener(listener);
}
}
return(ldm);
}
/**
* For all DiscoveryManagement instances this utility has created, terminate
* them and set the singleton instance to null;
*/
public void terminate() {
for(DiscoveryControl dc : pool) {
dc.terminate();
}
pool.clear();
singleton = null;
}
/*
* Get a DiscoveryControl instance
*/
private DiscoveryControl getDiscoveryControl(String name) {
DiscoveryControl discoControl=null;
synchronized(pool) {
for(DiscoveryControl dc : pool) {
if(dc.namesMatch(name)) {
discoControl = dc;
break;
}
}
}
return(discoControl);
}
/**
* Maintains a collection of LookupDiscoveryManager instances
*/
public static class DiscoveryControl {
String sharedName;
final List<SharedDiscoveryManager> pool = Collections.synchronizedList(new ArrayList<SharedDiscoveryManager>());
DiscoveryControl(String sharedName) {
this.sharedName = sharedName;
}
/*
* Determine if names match
*/
boolean namesMatch(String name) {
if(sharedName == null && name == null)
return (true);
if(sharedName == null)
return (false);
if(name == null)
return (false);
return(sharedName.equals(name));
}
String getSharedName() {
return(sharedName);
}
/*
* Add a LookupDiscoveryManager instance to the pool
*/
void addLookupDiscoveryManager(SharedDiscoveryManager ldm) {
if(pool!=null)
pool.add(ldm);
}
/*
* Remove a LookupDiscoveryManager instance from the pool
*/
void removeLookupDiscoveryManager(SharedDiscoveryManager ldm) {
if(pool!=null)
pool.remove(ldm);
}
/*
* Create a LookupDiscoveryManager instance
*/
LookupDiscoveryManager createLookupDiscoveryManager(String[] groups,
LookupLocator[] locators,
DiscoveryListener listener,
Configuration config) throws IOException {
if(logger.isDebugEnabled()) {
StringBuilder buffer = new StringBuilder();
if(groups==null) {
buffer.append("Create new SharedDiscoveryManager for ALL_GROUPS");
} else {
buffer.append("Create new SharedDiscoveryManager for groups [");
for(int i=0; i<groups.length; i++) {
if(i>0)
buffer.append(", ");
if(groups[i].equals(""))
buffer.append("[public]");
else
buffer.append(groups[i]);
}
buffer.append("], ");
}
buffer.append("shared name [").append(sharedName).append("], ");
if(config == null) {
buffer.append("using null config");
logger.debug(buffer.toString());
} else {
buffer.append("using config {}");
logger.warn(buffer.toString(), config.toString());
}
}
SharedDiscoveryManager ldm;
Configuration configToUse = (config==null?EmptyConfiguration.INSTANCE:config);
String className = SharedDiscoveryManager.class.getName();
try {
className = (String)configToUse.getEntry(COMPONENT,
"sharedDiscoveryManager",
String.class,
SharedDiscoveryManager.class.getName());
} catch (ConfigurationException e) {
logger.warn("Error obtaining the "+COMPONENT+".sharedDiscoveryManager property, defaulting to "+className, e);
}
try {
ldm = createSharedDiscoveryManager(className, groups, locators, listener, configToUse);
} catch (ConfigurationException e) {
logger.warn("Creating SharedDiscoveryManager with Configuration", e);
ldm = createSharedDiscoveryManager(className, groups, locators, listener);
} catch (IOException e) {
logger.warn("Could not create SharedDiscoveryManager", e);
throw e;
}
if(ldm!=null)
ldm.incrementRefCounter();
addLookupDiscoveryManager(ldm);
return(ldm);
}
private SharedDiscoveryManager createSharedDiscoveryManager(String classname,
String[] groups,
LookupLocator[] locators,
DiscoveryListener listener)
throws IOException {
ClassLoader cL = getClass().getClassLoader();
SharedDiscoveryManager ldm = null;
Throwable thrown = null;
try {
Class<?> ldmClass = cL.loadClass(classname);
Constructor cons = ldmClass.getConstructor(DiscoveryControl.class,
String[].class,
LookupLocator[].class,
DiscoveryListener.class);
ldm = (SharedDiscoveryManager)cons.newInstance(this,
groups,
locators,
listener);
} catch (ClassNotFoundException e) {
thrown = e;
} catch (IllegalAccessException e) {
thrown = e;
} catch (NoSuchMethodException e) {
thrown = e;
} catch (InvocationTargetException e) {
thrown = e;
} catch (InstantiationException e) {
thrown = e;
}
if(thrown!=null) {
logger.warn("Could not create "+classname+", defaulting to "+SharedDiscoveryManager.class.getName(), thrown);
ldm = new SharedDiscoveryManager(this, groups, locators, listener);
}
return ldm;
}
private SharedDiscoveryManager createSharedDiscoveryManager(String classname,
String[] groups,
LookupLocator[] locators,
DiscoveryListener listener,
Configuration config)
throws IOException, ConfigurationException {
ClassLoader cL = Thread.currentThread().getContextClassLoader();
SharedDiscoveryManager ldm = null;
Throwable thrown = null;
try {
Class<?> ldmClass = cL.loadClass(classname);
Constructor cons = ldmClass.getConstructor(DiscoveryControl.class,
String[].class,
LookupLocator[].class,
DiscoveryListener.class,
Configuration.class);
ldm = (SharedDiscoveryManager)cons.newInstance(this, groups, locators, listener, config);
} catch (ClassNotFoundException e) {
thrown = e;
} catch (IllegalAccessException e) {
thrown = e;
} catch (NoSuchMethodException e) {
thrown = e;
} catch (InvocationTargetException e) {
thrown = e;
} catch (InstantiationException e) {
thrown = e;
}
if(thrown!=null) {
logger.warn("Could not create "+classname+", defaulting to "+SharedDiscoveryManager.class.getName(), thrown);
ldm = new SharedDiscoveryManager(this, groups, locators, listener, config);
}
return ldm;
}
/**
* Terminate all LookupDiscoveryManager instances
*/
void terminate() {
SharedDiscoveryManager[] dms;
synchronized(pool) {
dms = pool.toArray(new SharedDiscoveryManager[pool.size()]);
}
for(DiscoveryManagement dm : dms) {
dm.terminate();
}
pool.clear();
}
/*
* Get a LookupDiscoveryManager instances based on the groups and locators
* If a match cannot be found return null
*/
LookupDiscoveryManager getLookupDiscoveryManager(String[] groupsToMatch, LookupLocator[] locatorsToMatch) {
SharedDiscoveryManager[] dms;
synchronized(pool) {
dms = pool.toArray(new SharedDiscoveryManager[pool.size()]);
}
for(LookupDiscoveryManager ldm : dms) {
String[] groups = ldm.getGroups();
/* If both are set to ALL_GROUPS we have a match */
if(groupsToMatch == LookupDiscovery.ALL_GROUPS && groups == LookupDiscovery.ALL_GROUPS) {
if(matchLocators(locatorsToMatch, ldm)) {
return(ldm);
}
}
/* If one or the other is set to ALL_GROUPS keep looking */
if(groupsToMatch == LookupDiscovery.ALL_GROUPS || groups == LookupDiscovery.ALL_GROUPS)
continue;
/* If both have the same "set", check for equivalence */
if(groupsToMatch.length == groups.length) {
int matches=0;
for(int i=0; i<groupsToMatch.length; i++) {
if(groupsToMatch[i].equals(groups[i]))
matches++;
}
if(matches==groupsToMatch.length) {
if(matchLocators(locatorsToMatch, ldm)) {
return(ldm);
}
}
}
}
return(null);
}
/*
* Determine if the locators match
*/
boolean matchLocators(LookupLocator[] locatorsToMatch, LookupDiscoveryManager ldm) {
boolean matched=false;
LookupLocator[] locators = ldm.getLocators();
if(locatorsToMatch == null && locators == null)
return(true);
if(locatorsToMatch == null && locators.length==0)
return(true);
if(locatorsToMatch!=null && locators!=null) {
if(locatorsToMatch.length == locators.length) {
int matches=0;
for(int i=0; i<locatorsToMatch.length; i++) {
if(locatorsToMatch[i].equals(locators[i]))
matches++;
}
if(matches==locatorsToMatch.length)
matched=true;
}
}
return(matched);
}
}
/**
* The SharedDiscoveryManager extends LookupDiscoveryManager and maintains a
* reference counter for how many clients are sharing the instance. The reference
* counter is used to determine if the LookupDiscoveryManager should be
* terminated. The reference counter is incremented each time this instance is
* shared, and decremented each time the terminate method is called. If the
* reference counter goes to zero upon termination the LookupDiscoveryManager
* will be terminated
*/
public static class SharedDiscoveryManager extends LookupDiscoveryManager {
private AtomicInteger refCounter = new AtomicInteger();
private DiscoveryControl discoControl;
public SharedDiscoveryManager(DiscoveryControl discoControl,
String[] groups,
LookupLocator[] locators,
DiscoveryListener listener) throws IOException {
super(groups, locators, listener);
this.discoControl = discoControl;
}
public SharedDiscoveryManager(DiscoveryControl discoControl,
String[] groups,
LookupLocator[] locators,
DiscoveryListener listener,
Configuration config) throws IOException, ConfigurationException {
super(groups, locators, listener, config);
this.discoControl = discoControl;
}
/*
* Get the sharedName property
*/
public String getSharedName() {
return(discoControl.getSharedName());
}
/*
* Increment the references
*/
synchronized void incrementRefCounter() {
refCounter.incrementAndGet();
}
/**
* Override parent's terminate method. Only call
* LookupDiscoveryManager.terminate() if there are no clients or users.
*/
public void terminate() {
synchronized(this) {
int counter = refCounter.decrementAndGet();
if(counter==0) {
super.terminate();
discoControl.removeLookupDiscoveryManager(this);
}
}
}
}
}