/* * Copyright to 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.client; import com.sun.jini.lookup.entry.LookupAttributes; import net.jini.admin.Administrable; import net.jini.admin.JoinAdmin; import net.jini.config.Configuration; import net.jini.config.ConfigurationException; import net.jini.config.EmptyConfiguration; import net.jini.core.discovery.LookupLocator; import net.jini.core.entry.Entry; import net.jini.core.lookup.ServiceID; import net.jini.core.lookup.ServiceItem; import net.jini.core.lookup.ServiceTemplate; import net.jini.discovery.DiscoveryManagement; import net.jini.discovery.LookupDiscovery; import net.jini.id.Uuid; import net.jini.lease.LeaseRenewalManager; import net.jini.lookup.*; import net.jini.lookup.entry.Name; import org.rioproject.deploy.ServiceBeanInstance; import org.rioproject.deploy.ServiceRecord; import org.rioproject.impl.container.ServiceBeanContainer; import org.rioproject.impl.container.ServiceBeanContainerListener; import org.rioproject.impl.container.ServiceBeanDelegate; import org.rioproject.impl.opstring.OpStringFilter; import org.rioproject.opstring.ClassBundle; import org.rioproject.servicecore.Service; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.rmi.RemoteException; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; /** * The LookupCachePool class provides the support to get an existing * LookupCache from a pool of created LookupCache instances. Criteria for * determining LookupCache matching is based on ServiceTemplate matching * * @author Dennis Reedy */ public class LookupCachePool { private final List<SDMWrapper> pool = new ArrayList<SDMWrapper>(); private ServiceBeanContainerListener containerListener; private static final String COMPONENT = LookupCachePool.class.getName(); private static final Logger logger = LoggerFactory.getLogger(COMPONENT); private static Configuration config; private static LookupCachePool singleton = new LookupCachePool(); private LookupCachePool() { logger.debug("Create new LookupCachePool"); } /** * Get the singleton instance of the LookupCachePool * * @return An instance of the LookupCachePool */ public static synchronized LookupCachePool getInstance() { return(singleton); } /** * Set the {@link org.rioproject.impl.container.ServiceBeanContainer} * * @param container The <tt><ServiceBeanContainer/tt> used for local * discovery */ public void setServiceBeanContainer(final ServiceBeanContainer container) { if(container !=null && containerListener==null) { containerListener = new ContainerListener(container); } } /** * Set the Configuration property * * @param conf The Configuration to use when creating * ServiceDiscoveryManager instances */ public void setConfiguration(final Configuration conf) { config = conf; logger.debug("Set configuration for LookupCachePool {}", config); } /** * This method will return an instance of LookupCache based on matching the * DiscoveryManagement instance and ServiceTemplate provided as * criteria. If there is an existing LookupCache instance created by a * ServiceDiscoveryManager instance this utility has created that matches the * supplied criteria, that instance will be returned. * * <p>If the LookupCache can not be found due to not being able to match discovery * criteria to a known ServiceDiscoveryManager instance a new * ServiceDiscoveryManager instance will be created, then a LookupCache instance * created and returned. * * <p>If a ServiceDiscoveryManager can be matched, but not a LookupCache, a new * LookupCache will be created using the matched ServiceDiscoveryManager * * @param dMgr A DiscoveryManager instance created by the DiscoveryManagementPool * @param template ServiceTemplate to match * @return A ServiceDiscoveryManager object based on the provided parameters or * null if the DiscoveryManagement instance was not created by the * DiscoveryManagementPool * * @throws IOException If discovery management cannot be created */ public LookupCache getLookupCache(final DiscoveryManagement dMgr, final ServiceTemplate template) throws IOException { if(!(dMgr instanceof DiscoveryManagementPool.SharedDiscoveryManager)) { logger.warn("The DiscoveryManagement instance passed was not created by the {}, returning null", DiscoveryManagementPool.class.getName()); return(null); } DiscoveryManagementPool.SharedDiscoveryManager sharedDM = (DiscoveryManagementPool.SharedDiscoveryManager)dMgr; return(getLookupCache(sharedDM.getSharedName(), sharedDM.getGroups(), sharedDM.getLocators(), template)); } /** * This method will return an instance of LookupCache based on matching the * shared name, shared discovery name, groups, locators and ServiceTemplate * provided as criteria. If there is an existing LookupCache instance created * by a ServiceDiscoveryManager instance this utility has created that matches * the supplied criteria, that instance will be returned. * * <p>If a LookupCache can not be found due to not being able to match discovery * criteria, a new ServiceDiscoveryManager instance will be created using the * provided discovery criteria, and a LookupCache instance created and returned. * * <p>If a ServiceDiscoveryManager can be matched, but not a LookupCache, a new * LookupCache will be created using the matched ServiceDiscoveryManager * * @param sharedName The name the LookupCache 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 template ServiceTemplate to match * @return A ServiceDiscoveryManager object based on the provided parameters * * @throws IOException If discovery management cannot be created */ public LookupCache getLookupCache(final String sharedName, final String[] groups, final LookupLocator[] locators, final ServiceTemplate template) throws IOException { if(template==null) throw new IllegalArgumentException("template is null"); SDMWrapper sdmWrapper; try { sdmWrapper = getSDMWrapper(sharedName, groups, locators); } catch(ConfigurationException e) { throw new IOException("Configuration problem creating a SDMWrapper", e); } return(sdmWrapper.getLookupCache(template, true)); } /** * For all ServiceDiscoveryManager instances this utility has created, terminate * them and set the singleton instance to null; */ public void terminate() { SDMWrapper[] sdms = getSDMWrappers(); for(SDMWrapper sdmWrapper : sdms) { sdmWrapper.sdm.terminate(); } pool.clear(); singleton = null; } private SDMWrapper[] getSDMWrappers() { SDMWrapper[] sdms; synchronized(pool) { sdms = pool.toArray(new SDMWrapper[pool.size()]); } return sdms; } /* * Get an SDMWrapper which matches the sharedName, discovery criteria, or * create one if not found */ private SDMWrapper getSDMWrapper(final String sharedName, final String[] groupsToMatch, final LookupLocator[] locatorsToMatch) throws IOException, ConfigurationException { SDMWrapper sdmWrapper = null; SDMWrapper[] sdms = getSDMWrappers(); for(SDMWrapper wrapper : sdms) { if(wrapper.namesMatch(sharedName) && wrapper.groupsMatch(groupsToMatch) && wrapper.locatorsMatch(locatorsToMatch)) { sdmWrapper = wrapper; break; } } if(sdmWrapper==null) { config = (config==null?EmptyConfiguration.INSTANCE:config); ServiceDiscoveryManager sdm = new ServiceDiscoveryManager(DiscoveryManagementPool.getInstance().getDiscoveryManager(sharedName, groupsToMatch, locatorsToMatch), new LeaseRenewalManager(config), config); sdmWrapper = new SDMWrapper(sharedName, sdm, groupsToMatch, locatorsToMatch); synchronized(pool) { pool.add(sdmWrapper); } } return(sdmWrapper); } /** * The SDMWrapper class provides a wrapper around a known ServiceDiscoveryManager, * the lookupCache instances that this utility has created using the * ServiceDiscoveryManager and checks to see if the referenced * ServiceDiscoveryManager matches specified criteria, or if any known * LookupCache instances match criteria */ class SDMWrapper { final String sharedName; final ServiceDiscoveryManager sdm; String[] groups; final LookupLocator[] locators; final Hashtable<ServiceTemplate, SharedLookupCache> cacheTable = new Hashtable<ServiceTemplate, SharedLookupCache>(); SDMWrapper(final String sharedName, final ServiceDiscoveryManager sdm, final String[] groups, final LookupLocator[] locators) { this.sharedName = sharedName; this.sdm = sdm; if(groups!=null) { this.groups = new String[groups.length]; System.arraycopy(groups, 0, this.groups, 0, this.groups.length); } this.locators = new LookupLocator[locators.length]; System.arraycopy(locators, 0, this.locators, 0, this.locators.length); } void removeCache(final SharedLookupCache lCache) { ServiceTemplate templateToMatch = lCache.getServiceTemplate(); for(Enumeration<ServiceTemplate> en=cacheTable.keys(); en.hasMoreElements();) { ServiceTemplate template = en.nextElement(); if(templatesMatch(template, templateToMatch)) { cacheTable.remove(templateToMatch); break; } } logger.trace("removeCache(), cacheTable.size()=={}",cacheTable.size()); if(cacheTable.size()==0) { try { sdm.terminate(); } catch (IllegalStateException e) { logger.trace("Terminating SDM", e); } synchronized(pool) { pool.remove(this); } } } /* * See if the sharedNames match * * @param name * * @return true if they do, false otherwise */ boolean namesMatch(final String name) { if(sharedName==null && name==null) return(true); if(sharedName==null) return(false); if(name == null) return (false); return(sharedName.equals(name)); } /** * See if the groups provided match the groups for the referenced SDM * * @param groupsToMatch The groups * * @return true if they do, false otherwise */ boolean groupsMatch(final String[] groupsToMatch) { /* If both are set to ALL_GROUPS we have a match */ if(groupsToMatch == LookupDiscovery.ALL_GROUPS && groups == LookupDiscovery.ALL_GROUPS) { return(true); } /* If one or the other is set to ALL_GROUPS return false */ if(groupsToMatch == LookupDiscovery.ALL_GROUPS || groups == LookupDiscovery.ALL_GROUPS) return(false); /* 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) { return(true); } } return(false); } /** * See if the locators provided match the locators for the referenced SDM * * @param locatorsToMatch The locators * * @return true if they do. false otherwise */ boolean locatorsMatch(final LookupLocator[] locatorsToMatch) { boolean matched=false; 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); } /** * Get a LookupCache from the cacheTable for the provided ServiceTemplate. * * @param templateToMatch The template * @param create If true and a a LookupCache does not exist, create one * * @return A SharedLookupCache for the ServiceTemplate * * @throws IOException If a LookupCache cannot be created */ SharedLookupCache getLookupCache(final ServiceTemplate templateToMatch, final boolean create) throws IOException { SharedLookupCache lCache = null; for(Enumeration en=cacheTable.keys(); en.hasMoreElements();) { ServiceTemplate template = (ServiceTemplate)en.nextElement(); if(templatesMatch(template, templateToMatch)) { lCache = cacheTable.get(template); break; } } if(lCache==null && create) { ServiceItemFilter filter = (sharedName==null?null: new OpStringFilter(sharedName)); LookupCache lc = sdm.createLookupCache(templateToMatch, filter, null); lCache = new SharedLookupCache(lc, templateToMatch, this); lCache.setServiceItemFilter(filter); cacheTable.put(templateToMatch, lCache); } return(lCache); } /** * Determine of the ServiceTemplate instances match each other * * @param st1 ServiceTemplate instance 1 * @param st2 ServiceTemplate instance 2 * * @return true if they match */ boolean templatesMatch(final ServiceTemplate st1, final ServiceTemplate st2) { if(attributesMatch(st1.attributeSetTemplates, st2.attributeSetTemplates) && serviceIDsMatch(st1.serviceID, st2.serviceID) && serviceTypesMatch(st1.serviceTypes, st2.serviceTypes)) { return(true); } return(false); } } /* * Check if attributes match */ boolean attributesMatch(final Entry[] attr1, final Entry[] attr2) { if(attr1==null && attr2==null) return(true); if(attr1==null || attr2==null) return(false); return(LookupAttributes.equal(attr1, attr2)); } /* * Check if service ID match */ boolean serviceIDsMatch(final ServiceID sid1, final ServiceID sid2) { if(sid1==null && sid2==null) return(true); if(sid1==null || sid2==null) return(false); return(sid1.equals(sid2)); } /* * Check if service types match */ boolean serviceTypesMatch(final Class[] types1, final Class[] types2) { if(types1==null && types2==null) return(true); if(types1==null || types2==null) return(false); if(types1.length == types2.length) { int matches = 0; for (Class c1 : types1) { for (Class c2 : types2) { if (c1.getName().equals(c2.getName())) matches++; } } if(matches==types1.length) return(true); } return(false); } /** * The SharedLookupCache implements a LookupCache and delegates all method * invocations to it's 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 increments 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 class SharedLookupCache implements LookupCache { private final LookupCache lCache; private final ServiceTemplate template; private final AtomicInteger refCounter = new AtomicInteger(); private final SDMWrapper sdmWrapper; private boolean terminated = false; private ServiceItemFilter filter; private final List<ServiceDiscoveryListener> localListeners = new ArrayList<ServiceDiscoveryListener>(); public SharedLookupCache(final LookupCache lCache, final ServiceTemplate template, final SDMWrapper sdmWrapper) { this.lCache = lCache; this.template = template; this.sdmWrapper = sdmWrapper; } void setServiceItemFilter(final ServiceItemFilter filter) { this.filter = filter; } /* * Get the ServiceTemplate */ private ServiceTemplate getServiceTemplate() { return(template); } /* (non-Javadoc) * @see net.jini.lookup.LookupCache#lookup(ServiceItemFilter) */ public ServiceItem lookup(final ServiceItemFilter filter) { return(lCache.lookup(filter)); } /* (non-Javadoc) * @see LookupCache#lookup(ServiceItemFilter, int) */ public ServiceItem[] lookup(final ServiceItemFilter filter, final int maxMatches) { return(lCache.lookup(filter, maxMatches)); } /* (non-Javadoc) * @see ServiceDiscoveryManager#lookup(ServiceTemplate, int, ServiceItemFilter) */ public ServiceItem[] lookupRemote(final ServiceItemFilter filter, final int maxMatches) { return sdmWrapper.sdm.lookup(template, maxMatches, filter); } /* (non-Javadoc) * @see LookupCache#addListener(ServiceDiscoveryListener) */ public synchronized void addListener(final ServiceDiscoveryListener listener) { refCounter.incrementAndGet(); logger.trace("Added LookupCache Listener for template [{}], refCounter: {}", getServiceTemplateAsString(), refCounter.get()); synchronized(localListeners) { localListeners.add(listener); } lCache.addListener(listener); } /* (non-Javadoc) * @see LookupCache#removeListener(ServiceDiscoveryListener) */ public synchronized void removeListener(final ServiceDiscoveryListener listener) { if(!terminated) { synchronized(localListeners) { localListeners.remove(listener); } lCache.removeListener(listener); refCounter.decrementAndGet(); } logger.trace("Removed LookupCache Listener for template [{}], refCounter: {}", getServiceTemplateAsString(), refCounter.get()); logger.trace("lCache={} refCounter={}", lCache.toString(), refCounter.get()); if(refCounter.get()==0) { terminate(); } } /* (non-Javadoc) * @see LookupCache#discard(Object) */ public void discard(final Object o) { logger.trace("Discard {} from LookupCache", o.getClass().getName()); try { lCache.discard(o); } catch(IllegalStateException e) { logger.warn("Could not discard {}, {}", o.getClass().getName(), e.getMessage()); } } /* (non-Javadoc) * @see net.jini.lookup.LookupCache#terminate() */ public synchronized void terminate() { if(refCounter.get()==0) { logger.trace("Terminating LookupCache for template [{}], refCounter: {}", getServiceTemplateAsString(), refCounter.get()); terminated = true; lCache.terminate(); sdmWrapper.removeCache(this); } } String getServiceTemplateAsString() { StringBuilder sb1 = new StringBuilder(); StringBuilder sb2 = new StringBuilder(); if(template.serviceTypes!=null) { for(Class c : template.serviceTypes) { if(sb2.length()>0) sb2.append(", "); sb2.append(c.getName()); } } sb1.append("types: [").append(sb2.toString()).append("] "); sb2.delete(0, sb2.length()); if(template.attributeSetTemplates!=null) { for(Entry e : template.attributeSetTemplates) { if(sb2.length()>0) sb2.append(", "); if(e instanceof Name) sb2.append("Name: ").append(((Name)e).name); else sb2.append(e.getClass().getName()); } } sb1.append("attributes: [").append(sb2.toString()).append("] "); return sb1.toString(); } void notifyOnLocalAdd(final ServiceItem item) { boolean cleared = true; if(filter!=null) { cleared = filter.check(item); } if(cleared) { ServiceDiscoveryListener[] listeners; synchronized(localListeners) { listeners = localListeners.toArray(new ServiceDiscoveryListener[localListeners.size()]); } for(ServiceDiscoveryListener l : listeners) { l.serviceAdded(new ServiceDiscoveryEvent(this, null, item)); } } } } class ContainerListener implements ServiceBeanContainerListener { private final ServiceBeanContainer container; private final List<ServiceRecord> notified = new ArrayList<ServiceRecord>(); ContainerListener(final ServiceBeanContainer container) { this.container = container; container.addListener(this); } private ServiceBeanInstance getServiceBeanInstance(final ServiceRecord r) { ServiceBeanDelegate delegate = container.getServiceBeanDelegate(r.getServiceID()); return delegate.getServiceBeanInstance(); } List<Class<?>> getAllInterfaces(Class<?> classObject) { List<Class<?>> list = new ArrayList<Class<?>>(); if(classObject.getName().equals(Object.class.getName())) return list; list.addAll(getAllInterfaces(classObject.getSuperclass())); Collections.addAll(list, classObject.getInterfaces()); return list; } public void serviceInstantiated(final ServiceRecord record) { try { ServiceBeanInstance instance = getServiceBeanInstance(record); Class<?> proxyClass = instance.getService().getClass(); Class<?> theInterfaceClass = null; for(Class<?> interfaceClass : getAllInterfaces(proxyClass)) { for(ClassBundle cb : record.getServiceElement().getExportBundles()) { if(interfaceClass.getName().equals(cb.getClassName())) { theInterfaceClass = interfaceClass; break; } } if(theInterfaceClass!=null) break; } if(theInterfaceClass==null) { logger.warn("No matching interface class found for {}, defaulting to {}", record.getServiceElement().getName(), Service.class.getName()); theInterfaceClass = Service.class; } logger.trace("[{}] selected: {}", record.getServiceElement().getName(), theInterfaceClass.getName()); ServiceTemplate templateToMatch = JiniClient.getServiceTemplate(record.getServiceElement(), theInterfaceClass); SDMWrapper[] sdms = getSDMWrappers(); for(SDMWrapper sdm : sdms) { SharedLookupCache lCache = sdm.getLookupCache(templateToMatch, false); if(lCache!=null) { boolean alreadyNotified; synchronized(notified) { alreadyNotified = notified.contains(record); } if(!alreadyNotified) { logger.trace("Notify listeners of local instantiation of {} {}", record.getServiceElement().getName(), theInterfaceClass.getName()); lCache.notifyOnLocalAdd(makeServiceItem(instance)); synchronized(notified) { notified.add(record); } } } } } catch (Exception e) { logger.warn("Unable to load service interface", e); } } public void serviceDiscarded(final ServiceRecord record) { synchronized(notified) { notified.remove(record); } } private ServiceItem makeServiceItem(final ServiceBeanInstance instance) throws IOException, ClassNotFoundException { Uuid uuid = instance.getServiceBeanID(); ServiceID serviceID = new ServiceID(uuid.getMostSignificantBits(), uuid.getLeastSignificantBits()); Entry[] attrs = null; Object service = instance.getService(); if(service instanceof Administrable) { try { Object admin = ((Administrable)service).getAdmin(); if(admin instanceof JoinAdmin) { attrs = ((JoinAdmin)admin).getLookupAttributes(); } } catch(RemoteException e) { logger.warn("Getting attributes from [{}]", instance.getServiceBeanConfig().getName(), e); } } return(new ServiceItem(serviceID, service, attrs)); } } }