/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 com.sun.jini.outrigger; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.rmi.MarshalledObject; import java.rmi.RemoteException; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import net.jini.core.discovery.LookupLocator; import net.jini.core.entry.Entry; import net.jini.core.lookup.ServiceID; import net.jini.discovery.DiscoveryManagement; import net.jini.discovery.DiscoveryLocatorManagement; import net.jini.discovery.DiscoveryGroupManagement; import net.jini.discovery.LookupDiscoveryManager; import net.jini.lookup.JoinManager; import net.jini.config.Configuration; import net.jini.config.ConfigurationException; import net.jini.security.ProxyPreparer; import com.sun.jini.config.Config; import com.sun.jini.logging.Levels; /** * <code>JoinStateManager</code> provides a utility that manages * a service's join state (optionally persisting that state) and * manages the join protocol protocol on behalf of the service. * * @author Sun Microsystems, Inc. * * @see com.sun.jini.lookup.JoinManager * @see JoinAdminState */ class JoinStateManager implements StorableObject { /** <code>ProxyPreparer</code> for <code>LookupLocators</code> */ private ProxyPreparer lookupLocatorPreparer; /** * Object used to find lookups. Has to implement DiscoveryManagement * and DiscoveryLocatorManagement as well as DiscoveryGroupManagement. */ private DiscoveryGroupManagement dgm; /** * <code>JoinManager</code> that is handling the details of binding * into Jini lookup services. */ private JoinManager mgr; /** * The object that is coordinating our persistent state. */ private LogOps log; /** * The list of attributes to start with. This field is only used * to carry data from the <code>restore</code> method to the * <code>startManager</code> method. The current set of attributes * is kept by <code>mgr</code>. This field is nulled out by * <code>startManager</code>. */ private Entry[] attributes; /** * The list of <code>LookupLocator</code>s to start with. This * field is only used to carry data from the <code>restore</code> * method to the <code>startManager</code> method. The current set * of attributes is kept by <code>mgr</code>. This field is nulled * out by <code>startManager</code>. */ private LookupLocator locators[]; /** * The list of group names to start with. This field is only used * to carry data from the <code>restore</code> method to the * <code>startManager</code> method. The current set of attributes * is kept by <code>mgr</code>. This field is nulled out by * <code>startManager</code>. */ private String groups[]; /** * Conceptually, true if this is the first time this * service has come up, implemented as if there was * no previous state then this is the first time. */ private boolean initial = true; /** Logger for logging join related information */ private static final Logger logger = Logger.getLogger(OutriggerServerImpl.joinLoggerName); /** * Simple constructor. */ JoinStateManager() { } /** * Start the manager. Start looking for lookup and registering * with them. * @param config object to use to obtain * <code>DiscoveryManagement</code> object, and if * this is the initial incarnation of this service, * the object used to get the initial set of groups, * locators, and deployer defined attributes. * @param log object used to persist the manager's state, may be * <code>null</code>. * @param serviceID The <code>ServiceID</code> to register * under. * @param service The proxy object to register with lookups. * @param baseAttributes Any attributes the implementation wants * attached, only used if this is the * initial incarnation. * @throws IOException if the is problem persisting the * initial state or in starting discovery. * @throws ConfigurationException if the configuration * is invalid. * @throws NullPointerException if <code>config</code>, * or <code>serviceID</code> is <code>null</code>. */ void startManager(Configuration config, LogOps log, Object service, ServiceID serviceID, Entry[] baseAttributes) throws IOException, ConfigurationException { // Default do nothing preparer final ProxyPreparer defaultPreparer = new net.jini.security.BasicProxyPreparer(); if (serviceID == null) throw new NullPointerException("serviceID can't be null"); this.log = log; lookupLocatorPreparer = (ProxyPreparer)Config.getNonNullEntry(config, OutriggerServerImpl.COMPONENT_NAME, "lookupLocatorPreparer", ProxyPreparer.class, defaultPreparer); dgm = (DiscoveryGroupManagement) Config.getNonNullEntry(config, OutriggerServerImpl.COMPONENT_NAME, "discoveryManager", DiscoveryGroupManagement.class, new LookupDiscoveryManager( DiscoveryGroupManagement.NO_GROUPS, null, null, config)); if (!(dgm instanceof DiscoveryManagement)) throw throwNewConfigurationException("Entry for component " + OutriggerServerImpl.COMPONENT_NAME + ", name " + "discoveryManager must implement " + "net.jini.discovery.DiscoveryGroupManagement"); if (!(dgm instanceof DiscoveryLocatorManagement)) throw throwNewConfigurationException("Entry for component " + OutriggerServerImpl.COMPONENT_NAME + ", name " + "discoveryManager must implement " + "net.jini.discovery.DiscoveryLocatorManagement"); final String[] toCheck = dgm.getGroups(); if (toCheck == null || toCheck.length != 0) throw throwNewConfigurationException("Entry for component " + OutriggerServerImpl.COMPONENT_NAME + ", name " + "discoveryManager must be initially configured with no " + "groups"); if (((DiscoveryLocatorManagement)dgm).getLocators().length != 0) throw throwNewConfigurationException("Entry for component " + OutriggerServerImpl.COMPONENT_NAME + ", name " + "discoveryManager must be initially configured with no " + "locators"); // if this is the first incarnation, consult config for groups, // locators and attributes. if (initial) { groups = (String[]) config.getEntry(OutriggerServerImpl.COMPONENT_NAME, "initialLookupGroups", String[].class, new String[]{""}); locators = (LookupLocator[]) Config.getNonNullEntry(config, OutriggerServerImpl.COMPONENT_NAME, "initialLookupLocators", LookupLocator[].class, new LookupLocator[0]); final Entry[] cAttrs = (Entry[]) Config.getNonNullEntry(config, OutriggerServerImpl.COMPONENT_NAME, "initialLookupAttributes", Entry[].class, new Entry[0]); if (cAttrs.length == 0) { attributes = baseAttributes; } else { attributes = new Entry[cAttrs.length + baseAttributes.length]; System.arraycopy(baseAttributes, 0, attributes, 0, baseAttributes.length); System.arraycopy(cAttrs, 0, attributes, baseAttributes.length, cAttrs.length); } } else { /* recovery : if there are any locators get and * use recoveredLookupLocatorPreparer */ if (locators.length > 0) { final ProxyPreparer recoveredLookupLocatorPreparer = (ProxyPreparer)Config.getNonNullEntry(config, OutriggerServerImpl.COMPONENT_NAME, "recoveredLookupLocatorPreparer", ProxyPreparer.class, defaultPreparer); final List prepared = new java.util.LinkedList(); for (int i=0; i<locators.length; i++) { final LookupLocator locator = locators[i]; try { prepared.add(recoveredLookupLocatorPreparer. prepareProxy(locator)); } catch (Throwable t) { logger.log(Level.INFO, "Encountered exception preparing lookup locator " + "for " + locator + ", dropping locator", t); } } locators = (LookupLocator[])prepared.toArray(new LookupLocator[0]); } } // Now that we have groups & locators (either from // a previous incarnation or from the config) start discovery. if (logger.isLoggable(Level.CONFIG)) { if (groups == null) { logger.log(Level.CONFIG, "joining all groups"); } else if (groups.length == 0) { logger.log(Level.CONFIG, "joining no groups"); } else { final StringBuffer buf = new StringBuffer(); buf.append("joining groups:"); for (int i=0; i<groups.length; i++) { if (i != 0) buf.append(","); buf.append("\""); buf.append(groups[i]); buf.append("\""); } logger.log(Level.CONFIG, buf.toString()); } if (locators.length == 0) { logger.log(Level.CONFIG, "joining no specific registrars"); } else { final StringBuffer buf = new StringBuffer(); buf.append("joining the specific registrars:"); for (int i=0; i<locators.length; i++) { if (i != 0) buf.append(", "); buf.append(locators[i]); } logger.log(Level.CONFIG, buf.toString()); } if (attributes.length == 0) { logger.log(Level.CONFIG, "registering no attributes"); } else { final StringBuffer buf = new StringBuffer(); buf.append("registering the attributes:"); for (int i=0; i<attributes.length; i++) { if (i != 0) buf.append(", "); buf.append(attributes[i]); } logger.log(Level.CONFIG, buf.toString()); } } dgm.setGroups(groups); ((DiscoveryLocatorManagement)dgm).setLocators(locators); mgr = new JoinManager(service, attributes, serviceID, (DiscoveryManagement)dgm, null, config); // Once we are running we don't need the attributes, // locators, and groups fields, null them out (the // state is in the mgr and dgm. attributes = null; groups = null; locators = null; // Now that we have state, make sure it is written to disk. update(); } /** * Make a good faith attempt to terminate * discovery, and cancel any lookup registrations. */ public void destroy() { // Unregister with lookup // Terminate the JoinManager first so it will not call // into the dgm after it has been terminated. if (mgr != null) mgr.terminate(); if (dgm != null) ((DiscoveryManagement)dgm).terminate(); } /* Basically we are implementing JoinAdmin, for get methods we just * delegate to JoinManager, for the set methods we call * JoinManager to and then persist the change by calling the * appropriate method on our JoinAdminState. If the call on our * JoinAdminState throws an IOException we throw a runtime * exception since JoinAdmin methods don't let us throw a * IOException */ /** * Get the current attribute sets for the service. * * @return the current attribute sets for the service */ public Entry[] getLookupAttributes() { return mgr.getAttributes(); } /** * Add attribute sets for the service. The resulting set will be used * for all future joins. The attribute sets are also added to all * currently-joined lookup services. * * @param attrSets the attribute sets to add * @throws java.rmi.RuntimeException if the change can not be persisted. */ public void addLookupAttributes(Entry[] attrSets) { mgr.addAttributes(attrSets, true); update(); } /** * Modify the current attribute sets, using the same semantics as * ServiceRegistration.modifyAttributes. The resulting set will be used * for all future joins. The same modifications are also made to all * currently-joined lookup services. * * @param attrSetTemplates the templates for matching attribute sets * @param attrSets the modifications to make to matching sets * * @throws java.rmi.RuntimeException if the change can not be persisted. * @see net.jini.core.lookup.ServiceRegistration#modifyAttributes */ public void modifyLookupAttributes(Entry[] attrSetTemplates, Entry[] attrSets) { mgr.modifyAttributes(attrSetTemplates, attrSets, true); update(); } /** * Get the list of groups to join. An empty array means the service * joins no groups (as opposed to "all" groups). * * @return an array of groups to join. An empty array means the service * joins no groups (as opposed to "all" groups). * @see #setLookupGroups */ public String[] getLookupGroups() { return dgm.getGroups(); } /** * Add new groups to the set to join. Lookup services in the new * groups will be discovered and joined. * * @param groups groups to join * @throws java.rmi.RuntimeException if the change can not be persisted. * @see #removeLookupGroups */ public void addLookupGroups(String[] groups) { try { dgm.addGroups(groups); } catch (IOException e) { throw propagateIOException("Could not change groups", e); } update(); } /** * Remove groups from the set to join. Leases are cancelled at lookup * services that are not members of any of the remaining groups. * * @param groups groups to leave * @throws java.rmi.RuntimeException if the change can not be persisted. * @see #addLookupGroups */ public void removeLookupGroups(String[] groups) { dgm.removeGroups(groups); update(); } /** * Replace the list of groups to join with a new list. Leases are * cancelled at lookup services that are not members of any of the * new groups. Lookup services in the new groups will be discovered * and joined. * * @param groups groups to join * @throws java.rmi.RuntimeException if the change can not be persisted. * @see #getLookupGroups */ public void setLookupGroups(String[] groups) { try { dgm.setGroups(groups); } catch (IOException e) { throw propagateIOException("Could not change groups", e); } update(); } /** * Get the list of locators of specific lookup services to join. * * @return the list of locators of specific lookup services to join * @see #setLookupLocators */ public LookupLocator[] getLookupLocators() { return ((DiscoveryLocatorManagement)dgm).getLocators(); } /** * Add locators for specific new lookup services to join. The new * lookup services will be discovered and joined. * * @param locators locators of specific lookup services to join * @throws java.rmi.RuntimeException if the change can not be persisted. * @see #removeLookupLocators */ public void addLookupLocators(LookupLocator[] locators) throws RemoteException { prepareLocators(locators); ((DiscoveryLocatorManagement)dgm).addLocators(locators); update(); } /** * Remove locators for specific lookup services from the set to join. * Any leases held at the lookup services are cancelled. * * @param locators locators of specific lookup services to leave * @throws java.rmi.RuntimeException if the change can not be persisted. * @see #addLookupLocators */ public void removeLookupLocators(LookupLocator[] locators) throws RemoteException { prepareLocators(locators); ((DiscoveryLocatorManagement)dgm).removeLocators(locators); update(); } /** * Replace the list of locators of specific lookup services to join * with a new list. Leases are cancelled at lookup services that were * in the old list but are not in the new list. Any new lookup services * will be discovered and joined. * * @param locators locators of specific lookup services to join * @throws java.rmi.RuntimeException if the change can not be persisted. * @see #getLookupLocators */ public void setLookupLocators(LookupLocator[] locators) throws RemoteException { prepareLocators(locators); ((DiscoveryLocatorManagement)dgm).setLocators(locators); update(); } private void update() { if (log != null) log.joinStateOp(this); } /** * Apply <code>lookupLocatorPreparer</code> to each locator in the * array, replacing the original locator with the result of the * <code>prepareProxy</code> call. If call fails with an exception * throw that exception. * @param locators the <code>LookupLocator</code>s to be prepared. * @throws RemoteException if preparation of any of the locators * does. * @throws SecurityException if preparation of any of the locators * does. */ private void prepareLocators(LookupLocator[] locators) throws RemoteException { for (int i = 0; i<locators.length; i++) locators[i] = (LookupLocator)lookupLocatorPreparer.prepareProxy( locators[i]); } /** * Utility method to write out an array of entities to an * <code>ObjectOutputStream</code>. Can be recovered by a call * to <code>readAttributes()</code> * <p> * Packages each attribute in its own <code>MarshalledObject</code> so * a bad codebase on an attribute class will not corrupt the whole array. * @see JoinAdminActivationState#readAttributes */ static private void writeAttributes(Entry[] attributes, ObjectOutputStream out) throws IOException { // Need to package each attribute in its own marshaled object, // this makes sure that the attribute's code base is preserved // and when we unpack to discard attributes who's codebase // has been lost without throwing away those we can still deal with. out.writeInt(attributes.length); for (int i=0; i<attributes.length; i++) { out.writeObject(new MarshalledObject(attributes[i])); } } /** * Utility method to read in an array of entities from a * <code>ObjectInputStream</code>. Array should have been written * by a call to <code>writeAttributes()</code> * <p> * * Will try and recover as many attributes as possible. * Attributes which can't be recovered won't be returned but they * will remain in the log. * * @see JoinAdminActivationState#writeAttributes */ static private Entry[] readAttributes(ObjectInputStream in) throws IOException, ClassNotFoundException { final List entries = new java.util.LinkedList(); final int objectCount = in.readInt(); for (int i=0; i<objectCount; i++) { try { MarshalledObject mo = (MarshalledObject)in.readObject(); entries.add(mo.get()); } catch (IOException e) { logger.log(Level.INFO, "Encountered IOException recovering " + "attribute, dropping attribute", e); } catch (ClassNotFoundException e) { logger.log(Level.INFO, "Encountered ClassNotFoundException " + "recovering attribute, dropping attribute", e); } } return (Entry[])entries.toArray(new Entry[0]); } // ----------------------------------- // Methods required by StorableObject // ----------------------------------- // inherit doc comment public void store(ObjectOutputStream out) throws IOException { writeAttributes(mgr.getAttributes(), out); out.writeObject(((DiscoveryLocatorManagement)dgm).getLocators()); out.writeObject(dgm.getGroups()); } // inherit doc comment public void restore(ObjectInputStream in) throws IOException, ClassNotFoundException { initial = false; attributes = readAttributes(in); locators = (LookupLocator [])in.readObject(); groups = (String [])in.readObject(); } /** * Construct, log, and throw a new ConfigurationException with * the given message. */ private static ConfigurationException throwNewConfigurationException( String msg) throws ConfigurationException { final ConfigurationException e = new ConfigurationException(msg); if (logger.isLoggable(Levels.FAILED)) { logger.log(Levels.FAILED, msg, e); } throw e; } /** * Propagate an IOException by wrapping it in a RuntimeException. * Performs appropriate logging. */ private static RuntimeException propagateIOException(String msg, IOException nested) { final RuntimeException e = new RuntimeException(msg, nested); if (logger.isLoggable(Levels.FAILED)) { logger.log(Levels.FAILED, msg, e); } throw e; } }