/* * 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.mahalo; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInput; import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.rmi.MarshalledObject; import java.rmi.RemoteException; import java.util.Arrays; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import net.jini.config.Configuration; import net.jini.config.ConfigurationException; 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.id.Uuid; import net.jini.lookup.JoinManager; import net.jini.lookup.ServiceIDListener; import net.jini.security.ProxyPreparer; import com.sun.jini.config.Config; import com.sun.jini.logging.Levels; import com.sun.jini.reliableLog.LogHandler; import com.sun.jini.reliableLog.ReliableLog; /** * <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 net.jini.lookup.ServiceIDListener * @see com.sun.jini.reliableLog.LogHandler */ class JoinStateManager extends LogHandler { /** Logger for logging initialization related messages */ private static final Logger initlogger = TxnManagerImpl.initLogger; /** Logger for logging operations related messages */ private static final Logger operationsLogger = TxnManagerImpl.operationsLogger; /** Logger for transaction persistence related messages */ private static final Logger persistenceLogger = TxnManagerImpl.persistenceLogger; /** <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 DiscoveryManagement dm; /** * <code>JoinManager</code> that is handling the details of binding * into Jini lookup services. */ private JoinManager mgr; /** * The object coordinating our persistent state. */ private ReliableLog log; /** * The join state, this data needs to be persisted between restarts */ private Entry[] attributes; private LookupLocator[] locators; private String[] groups; /** Service's internal <code>Uuid</code> which needs to be persisted */ private Uuid serviceUuid; /** * 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; /** * Simple constructor. */ JoinStateManager(String logPath) throws IOException { super(); if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.entering(JoinStateManager.class.getName(), "JoinStateManager", logPath); } this.log = (logPath==null) ? null : new ReliableLog(logPath, this); if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.exiting(JoinStateManager.class.getName(), "JoinStateManager"); } } void recover() throws IOException { if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.entering(JoinStateManager.class.getName(), "recover"); } if (log != null) log.recover(); if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.exiting(JoinStateManager.class.getName(), "recover"); } } /** * 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 logPath Path to service's stable storage area. If * <code>null</code> then changes will not be persisted. * @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. */ void startManager(Configuration config, Object service, ServiceID serviceID, Entry[] baseAttributes) throws IOException, ConfigurationException { if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.entering(JoinStateManager.class.getName(), "startManager", new Object[] { config, service, serviceID, Arrays.asList(baseAttributes)}); } if (serviceID == null || serviceUuid == null) throw new AssertionError( "serviceID and serviceUuid must be set"); // Default do nothing preparer final ProxyPreparer defaultPreparer = new net.jini.security.BasicProxyPreparer(); lookupLocatorPreparer = (ProxyPreparer)Config.getNonNullEntry(config, TxnManager.MAHALO, "lookupLocatorPreparer", ProxyPreparer.class, defaultPreparer); if(initlogger.isLoggable(Level.CONFIG)) { initlogger.log(Level.CONFIG, "lookupLocatorPreparer: {0}", lookupLocatorPreparer); } //TODO - defer creation of default LDM dm = (DiscoveryManagement) Config.getNonNullEntry(config, TxnManager.MAHALO, "discoveryManager", DiscoveryManagement.class, new LookupDiscoveryManager( DiscoveryGroupManagement.NO_GROUPS, null, null, config)); if(initlogger.isLoggable(Level.CONFIG)) { initlogger.log(Level.CONFIG, "discoveryManager: {0}", dm); } if (dm instanceof DiscoveryGroupManagement) { // Verify proper initial state ---> NO_GROUPS String[] groups = ((DiscoveryGroupManagement)dm).getGroups(); if( (groups == DiscoveryGroupManagement.ALL_GROUPS) || (groups.length != 0) ) { throw new ConfigurationException( "discoveryManager entry must be configured " + "to initially discover/join NO_GROUPS"); }//endif } else { throw new ConfigurationException("Entry for component " + TxnManager.MAHALO + ", name " + "discoveryManager must implement " + "net.jini.discovery.DiscoveryGroupManagement"); } if (dm instanceof DiscoveryLocatorManagement) { LookupLocator[] locs = ((DiscoveryLocatorManagement)dm).getLocators(); if( (locs != null) && (locs.length != 0) ) { throw new ConfigurationException ("discoveryManager entry must be initially" + "configured with no locators"); }//endif } else { throw new ConfigurationException("Entry for component " + TxnManager.MAHALO + ", name " + "discoveryManager must implement " + "net.jini.discovery.DiscoveryLocatorManagement"); } // if this is the first incarnation, consult config for groups, // locators and attributes. if (initial) { if(initlogger.isLoggable(Level.FINEST)) { initlogger.log(Level.FINEST, "Obtaining initial values"); } groups = (String[]) config.getEntry(TxnManager.MAHALO, "initialLookupGroups", String[].class, new String[] { "" }); //default to public group if(initlogger.isLoggable(Level.CONFIG)) { initlogger.log(Level.CONFIG, "Obtaining initial groups: {0}", (groups==null? Arrays.asList(new String[] {"<ALL_GROUPS>"}): Arrays.asList(groups))); } locators = (LookupLocator[]) Config.getNonNullEntry(config, TxnManager.MAHALO, "initialLookupLocators", LookupLocator[].class, new LookupLocator[0]); if(initlogger.isLoggable(Level.CONFIG)) { initlogger.log(Level.CONFIG, "Obtaining initial locators: {0}", Arrays.asList(locators)); } final Entry[] cAttrs = (Entry[]) Config.getNonNullEntry(config, TxnManager.MAHALO, "initialLookupAttributes", Entry[].class, new Entry[0]); if(initlogger.isLoggable(Level.CONFIG)) { initlogger.log(Level.CONFIG, "Obtaining initial attributes: {0}", Arrays.asList(cAttrs)); } 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); } if(initlogger.isLoggable(Level.FINEST)) { initlogger.log(Level.FINEST, "Combined attributes: {0}", Arrays.asList(attributes)); } } else { /* recovery : if there are any locators get and * use recoveredLookupLocatorPreparer */ if(initlogger.isLoggable(Level.FINEST)) { initlogger.log(Level.FINEST, "Recovered locators: {0}", Arrays.asList(locators)); } if (locators.length > 0) { final ProxyPreparer recoveredLookupLocatorPreparer = (ProxyPreparer)Config.getNonNullEntry(config, TxnManager.MAHALO, "recoveredLookupLocatorPreparer", ProxyPreparer.class, defaultPreparer); if(initlogger.isLoggable(Level.CONFIG)) { initlogger.log(Level.CONFIG, "recoveredLookupLocatorPreparer: {0}", recoveredLookupLocatorPreparer); } final List prepared = new java.util.LinkedList(); for (int i=0; i<locators.length; i++) { try { prepared.add(recoveredLookupLocatorPreparer. prepareProxy(locators[i])); } catch (Throwable t) { if(initlogger.isLoggable(Levels.HANDLED)) { initlogger.log(Levels.HANDLED, "Exception re-preparing LookupLocator: {0}. " + "Dropping locator.", locators[i]); } if(initlogger.isLoggable(Levels.HANDLED)) { initlogger.log(Levels.HANDLED, "Preparer exception: ", 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(initlogger.isLoggable(Level.FINEST)) { initlogger.log(Level.FINEST, "Setting groups and locators"); } ((DiscoveryGroupManagement)dm).setGroups(groups); ((DiscoveryLocatorManagement)dm).setLocators(locators); if(initlogger.isLoggable(Level.FINEST)) { initlogger.log(Level.FINEST, "Creating JoinManager"); } mgr = new JoinManager(service, attributes, serviceID, dm, 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 dm. attributes = null; groups = null; locators = null; // Now that we have state, make sure it is written to disk. if(initlogger.isLoggable(Level.FINEST)) { initlogger.log(Level.FINEST, "Taking snapshot"); } update(); if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.exiting(JoinStateManager.class.getName(), "startManager"); } } public void setServiceUuid(Uuid serviceUuid) { if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.entering(JoinStateManager.class.getName(), "setServiceUuid", serviceUuid); } if (serviceUuid == null) throw new NullPointerException("serviceUuid can't be null"); this.serviceUuid = serviceUuid; // Can't update until mgr & dm are started. if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.exiting(JoinStateManager.class.getName(), "setServiceUuid"); } } public Uuid getServiceUuid() { if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.entering(JoinStateManager.class.getName(), "getServiceUuid"); } if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.exiting(JoinStateManager.class.getName(), "getServiceUuid", serviceUuid); } return serviceUuid; } /** * Make a good faith attempt to terminate * discovery, and cancel any lookup registrations. */ public void stop() { if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.entering(JoinStateManager.class.getName(), "stop"); } // Unregister with lookup // Terminate the JoinManager first so it will not call // into the dm after it has been terminated. if (mgr != null) mgr.terminate(); if (dm != null) dm.terminate(); if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.exiting(JoinStateManager.class.getName(), "stop"); } } public void destroy() { if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.entering(JoinStateManager.class.getName(), "destroy"); } stop(); if (log != null) log.deletePersistentStore(); if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.exiting(JoinStateManager.class.getName(), "destroy"); } } /* 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() { if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.entering(JoinStateManager.class.getName(), "getLookupAttributes"); } if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.exiting(JoinStateManager.class.getName(), "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) { if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.entering(JoinStateManager.class.getName(), "addLookupAttributes"); } mgr.addAttributes(attrSets, true); update(); if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.exiting(JoinStateManager.class.getName(), "addLookupAttributes"); } } /** * 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) { if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.entering(JoinStateManager.class.getName(), "modifyLookupAttributes"); } mgr.modifyAttributes(attrSetTemplates, attrSets, true); update(); if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.exiting(JoinStateManager.class.getName(), "modifyLookupAttributes"); } } /** * 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() { if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.entering(JoinStateManager.class.getName(), "getLookupGroups"); } if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.exiting(JoinStateManager.class.getName(), "getLookupGroups"); } return ((DiscoveryGroupManagement)dm).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) { if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.entering(JoinStateManager.class.getName(), "addLookupGroups"); } try { ((DiscoveryGroupManagement)dm).addGroups(groups); } catch (IOException e) { throw new RuntimeException("Could not change groups"); } update(); if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.exiting(JoinStateManager.class.getName(), "addLookupGroups"); } } /** * 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) { if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.entering(JoinStateManager.class.getName(), "removeLookupGroups"); } ((DiscoveryGroupManagement)dm).removeGroups(groups); update(); if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.exiting(JoinStateManager.class.getName(), "removeLookupGroups"); } } /** * 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) { if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.entering(JoinStateManager.class.getName(), "setLookupGroups"); } try { ((DiscoveryGroupManagement)dm).setGroups(groups); } catch (IOException e) { throw new RuntimeException("Could not change groups"); } update(); if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.exiting(JoinStateManager.class.getName(), "setLookupGroups"); } } /** * 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() { if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.entering(JoinStateManager.class.getName(), "getLookupLocators"); } if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.exiting(JoinStateManager.class.getName(), "getLookupLocators"); } return ((DiscoveryLocatorManagement)dm).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 { if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.entering(JoinStateManager.class.getName(), "addLookupLocators"); } prepareLocators(locators); ((DiscoveryLocatorManagement)dm).addLocators(locators); update(); if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.exiting(JoinStateManager.class.getName(), "addLookupLocators"); } } /** * 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 { if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.entering(JoinStateManager.class.getName(), "removeLookupLocators"); } prepareLocators(locators); ((DiscoveryLocatorManagement)dm).removeLocators(locators); update(); if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.exiting(JoinStateManager.class.getName(), "removeLookupLocators"); } } /** * 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 { if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.entering(JoinStateManager.class.getName(), "setLookupLocators"); } prepareLocators(locators); ((DiscoveryLocatorManagement)dm).setLocators(locators); update(); if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.exiting(JoinStateManager.class.getName(), "setLookupLocators"); } } /** * 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 { if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.entering(JoinStateManager.class.getName(), "prepareLocators"); } for (int i = 0; i<locators.length; i++) { locators[i] = (LookupLocator)lookupLocatorPreparer.prepareProxy( locators[i]); } if (operationsLogger.isLoggable(Level.FINER)) { operationsLogger.exiting(JoinStateManager.class.getName(), "prepareLocators"); } } private void update() { if (log != null) { synchronized (log) { try { log.snapshot(); } catch (IOException e) { if (persistenceLogger.isLoggable(Level.WARNING)) { persistenceLogger.log(Level.WARNING, "Failed to persist join state", e); } //TODO - need a better strategy here throw new RuntimeException("Problem persisting state.", e); } } } } /** * 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, ObjectOutput 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(ObjectInput 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) { if(initlogger.isLoggable(Levels.HANDLED)) { initlogger.log(Levels.HANDLED, "Exception getting service attribute ... skipping", e); } } catch (ClassNotFoundException e) { if(initlogger.isLoggable(Levels.HANDLED)) { initlogger.log(Levels.HANDLED, "Exception getting service attribute ... skipping", e); } } } return (Entry[])entries.toArray(new Entry[0]); } // ----------------------------------- // Methods required by LogHandler // ----------------------------------- // inherit doc comment public void snapshot(OutputStream out) throws IOException { ObjectOutputStream oostream = new ObjectOutputStream(out); oostream.writeObject(serviceUuid); writeAttributes(mgr.getAttributes(), oostream); oostream.writeObject(((DiscoveryLocatorManagement)dm).getLocators()); oostream.writeObject(((DiscoveryGroupManagement)dm).getGroups()); oostream.flush(); } // inherit doc comment public void recover(InputStream in) throws Exception { initial = false; ObjectInputStream oistream = new ObjectInputStream(in); serviceUuid = (Uuid)oistream.readObject(); attributes = readAttributes(oistream); locators = (LookupLocator [])oistream.readObject(); groups = (String [])oistream.readObject(); } /** * This method always throws * <code>UnsupportedOperationException</code> since * <code>FileJoinAdminState</code> should never update a * log. */ public void applyUpdate(Object update) throws Exception { throw new UnsupportedOperationException( "JoinStateManager:Updating log" + ", this should not happen"); } }