/* * 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.norm.lookup; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.rmi.MarshalledObject; import java.util.LinkedList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import java.util.logging.LogRecord; import net.jini.config.Configuration; import net.jini.config.ConfigurationException; import net.jini.config.NoSuchEntryException; import net.jini.core.discovery.LookupLocator; import net.jini.core.entry.Entry; import net.jini.core.lookup.ServiceID; import net.jini.discovery.DiscoveryGroupManagement; import net.jini.discovery.DiscoveryLocatorManagement; import net.jini.discovery.DiscoveryManagement; import net.jini.discovery.LookupDiscoveryManager; import net.jini.lease.LeaseRenewalManager; import net.jini.lookup.JoinManager; 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; /** * Utility class that combines <code>JoinManager</code> with persistence. * * @author Sun Microsystems, Inc. */ public class JoinState extends LogHandler implements SubStore { /** Logger and configuration component name for Norm */ private static final String NORM = "com.sun.jini.norm"; /** Logger for logging messages */ private static final Logger logger = Logger.getLogger(NORM); /** Service we are registering with lookup services */ final Object service; /** * Lease renewal manager (if any) that the client wants our * <code>JoinManager</code> to use. */ private final LeaseRenewalManager lrm; /** Configuration, to supply initial attributes, groups, and locators */ private final Configuration config; /** Attributes supplied by the service */ private final Entry[] serviceAttributes; /** Proxy preparer for recovered lookup locators */ private final ProxyPreparer recoveredLookupLocatorPreparer; /** The service ID, derived from the service's UUID. */ private final ServiceID serviceID; /** * Log we are using to persist our state to disk, or null if not * persistent. */ private ReliableLog log; /** * Set to true if there was existing persistent data that was recovered -- * used to determine if the JoinState is being created for the first time. */ private boolean recoveredData; /** * Pass through to get attribute array from the log recovery method * to the code that creates the <code>JoinManager</code> */ private Entry[] attributes; /** * Pass through to get lookup group array from the log * recovery method to the code that creates the * <code>JoinManager</code> */ private String groups[] = DiscoveryGroupManagement.NO_GROUPS; /** * Pass through to get lookup locator array from the log * recovery method to the code that creates the * <code>JoinManager</code> */ private LookupLocator locators[]; /** * The <code>DiscoveryManagement</code> we are using to find lookups, which * must also implement <code>DiscoveryGroupManagement</code> and * <code>DiscoveryLocatorManagement</code>. */ private DiscoveryManagement dm; /** Our join manager */ private JoinManager joinMgr; /** * Simple constructor. * @param service the object to register with lookup * @param lrm a <code>LeaseRenewalManager</code> to pass to the * <code>JoinManager</code>. May be <code>null</code>. * @param config a configuration that supplies initial attributes, groups, * and locators * @param serviceAttributes attributes supplied by the service * @param recoveredLookupLocatorPreparer proxy preparer for recovered * lookup locators * @param serviceID the service ID for the service */ public JoinState(Object service, LeaseRenewalManager lrm, Configuration config, Entry[] serviceAttributes, ProxyPreparer recoveredLookupLocatorPreparer, ServiceID serviceID) throws IOException { this.service = service; this.lrm = lrm; this.config = config; this.serviceAttributes = serviceAttributes; this.recoveredLookupLocatorPreparer = recoveredLookupLocatorPreparer; this.serviceID = serviceID; } //////////////////////////////////////////////// // Methods needed to meet the SubStore interface // Inherit JavaDoc from super-type public String subDirectory() { return "JoinState"; } // Inherit JavaDoc from super-type public void setDirectory(File dir) throws IOException, ConfigurationException { if (dir != null) { try { log = new ReliableLog(dir.getCanonicalPath(), this); log.recover(); } catch (IOException e) { IOException e2 = new IOException( "Log is corrupted: " + e.getMessage()); e2.initCause(e); throw e2; } } if (!recoveredData) { /* * Get initial attributes, groups, and locators, if not retrieved * from persistent storage. */ getInitialEntries(); } else { /* * Prepare recovered lookup locators, dropping ones for which * preparation fails. */ List prepared = new LinkedList(); for (int i = locators.length; --i >= 0; ) { try { prepared.add( recoveredLookupLocatorPreparer.prepareProxy( locators[i])); } catch (Throwable t) { if (logger.isLoggable(Level.INFO)) { logThrow(Level.INFO, "setDirectory", "Problem preparing lookup locator {0} -- " + "discarding", new Object[] { locators[i] }, t); } } } locators = (LookupLocator[]) prepared.toArray( new LookupLocator[prepared.size()]); } // Create DiscoveryManager createDiscoveryManager(); // Create JoinManager try { joinMgr = new JoinManager(service, attributes, serviceID, dm, lrm, config); } catch (IOException e) { IOException e2 = new IOException( "Problem starting JoinManager: " + e.getMessage()); e2.initCause(e2); throw e2; } // For now we are treating the state of the // JoinManager/LookupDiscoveryManager as truth for our // log, now that we have a lookup manager, force it into // sync with our log try { takeSnapshot(); } catch (IOException e) { logger.log(Level.WARNING, "Ignoring problem creating initial snapshot", e); } } /** Logs a throw */ private static void logThrow(Level level, String method, String msg, Object[] msgParams, Throwable t) { LogRecord r = new LogRecord(level, msg); r.setLoggerName(logger.getName()); r.setSourceClassName(JoinState.class.getName()); r.setSourceMethodName(method); r.setParameters(msgParams); r.setThrown(t); logger.log(r); } /** * Returns a configuration entry that is an array of objects, checking that * all the elements of the array are non-null. */ private Object[] getArrayEntry(String name, Class type, Object defaultValue) throws ConfigurationException { Object[] result = (Object[]) config.getEntry( NORM, name, type, defaultValue); if (result != null) { for (int i = result.length; --i >= 0; ) { if (result[i] == null) { throw new ConfigurationException( "Entry for component " + NORM + ", name " + name + " must not contain null elements"); } } } return result; } /** * Retrieves the initial values for attributes, groups, and locators from * the configuration. */ private void getInitialEntries() throws ConfigurationException { attributes = (Entry[]) getArrayEntry( "initialLookupAttributes", Entry[].class, null); if (attributes == null || attributes.length == 0) { attributes = serviceAttributes; } else { Entry[] temp = new Entry[ serviceAttributes.length + attributes.length]; System.arraycopy(serviceAttributes, 0, temp, 0, serviceAttributes.length); System.arraycopy(attributes, 0, temp, serviceAttributes.length, attributes.length); attributes = temp; } groups = (String[]) getArrayEntry( "initialLookupGroups", String[].class, new String[] { "" }); locators = (LookupLocator[]) getArrayEntry( "initialLookupLocators", LookupLocator[].class, null); if (locators == null) { locators = new LookupLocator[0]; } } /** Creates the discovery manager. */ private void createDiscoveryManager() throws ConfigurationException, IOException { try { dm = (DiscoveryManagement) Config.getNonNullEntry( config, NORM, "discoveryManager", DiscoveryManagement.class); if (!(dm instanceof DiscoveryGroupManagement)) { throw new ConfigurationException( "Entry for component " + NORM + ", name discoveryManager must implement " + "net.jini.discovery.DiscoveryGroupManagement"); } String[] groups = ((DiscoveryGroupManagement) dm).getGroups(); if (groups == null || groups.length != 0) { throw new ConfigurationException( "Entry for component " + NORM + ", name discoveryManager must be configured with no " + "groups"); } else if (!(dm instanceof DiscoveryLocatorManagement)) { throw new ConfigurationException( "Entry for component " + NORM + ", name discoveryManager must implement " + "net.jini.discovery.DiscoveryLocatorManagement"); } else if (((DiscoveryLocatorManagement) dm).getLocators().length != 0) { throw new ConfigurationException( "Entry for component " + NORM + ", name discoveryManager must be configured with no " + "locators"); } ((DiscoveryGroupManagement) dm).setGroups(groups); ((DiscoveryLocatorManagement) dm).setLocators(locators); } catch (NoSuchEntryException e) { dm = new LookupDiscoveryManager(groups, locators, null, config); } } // Inherit JavaDoc from super-type public void prepareDestroy() { try { if (log != null) log.close(); } catch (IOException e) { logger.log(Levels.HANDLED, "Ignoring problem closing log during destroy", e); } } /** * Terminate our participation in the Join and discovery * Protocols. Note, this method leaves the logs intact. */ public void terminateJoin() { // Terminate the JoinManager first so it will not call // into the dm after it has been terminated. if (joinMgr != null) joinMgr.terminate(); if (dm != null) dm.terminate(); } ////////////////////////////////////////////////// // Methods needed to meet the LogHandler interface // Inherit doc comment from super interface public void snapshot(OutputStream out) throws IOException { ObjectOutputStream oostream = new ObjectOutputStream(out); writeAttributes(joinMgr.getAttributes(), oostream); oostream.writeObject(((DiscoveryGroupManagement) dm).getGroups()); oostream.writeObject(((DiscoveryLocatorManagement) dm).getLocators()); oostream.flush(); } // Inherit doc comment from super interface public void recover(InputStream in) throws Exception { ObjectInputStream oistream = new ObjectInputStream(in); attributes = readAttributes(oistream); groups = (String[]) oistream.readObject(); locators = (LookupLocator[]) oistream.readObject(); recoveredData = true; } /** * This method always throws <code>UnsupportedOperationException</code> * since <code>JoinState</code> should never update a log. */ public void applyUpdate(Object update) throws Exception { throw new UnsupportedOperationException( "Recovering log update -- this should not happen"); } /** * 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. */ static private void writeAttributes(Entry[] attributes, ObjectOutputStream out) throws IOException { // Need to package each attribute in its own marshalled object. // This makes sure that the attribute's codebase is preserved // and, when we unpack, that we can discard attributes whose 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 (though they will be lost when the next * snapshot is taken). */ static private Entry[] readAttributes(ObjectInputStream in) throws IOException, ClassNotFoundException { final List entries = new 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, "Problem recovering attribute -- discarding", e); } catch (ClassNotFoundException e) { logger.log(Level.INFO, "Problem recovering attribute -- discarding", e); } } return (Entry[]) entries.toArray(new Entry[0]); } /** * Used by all the methods that change persistent state to * commit the change to disk */ private void takeSnapshot() throws IOException { if (log == null) { return; } synchronized (log) { log.snapshot(); } } ///////////////////////////////////////////////////////////////// // Effectively a clone of the JoinAdmin interface, used by // the client to implement JoinAdmin and to set its attributes /** * 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). */ public String[] getGroups() { 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 */ public void addGroups(String[] groups) { try { ((DiscoveryGroupManagement) dm).addGroups(groups); } catch (IOException e) { throw new RuntimeException( "Could not change groups: " + e.getMessage(), e); } try { takeSnapshot(); } catch (IOException e) { throw new RuntimeException( "Could not log change: " + e.getMessage(), e); } } /** * 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 */ public void removeGroups(String[] groups) { ((DiscoveryGroupManagement) dm).removeGroups(groups); try { takeSnapshot(); } catch (IOException e) { throw new RuntimeException( "Could not log change: " + e.getMessage(), e); } } /** * 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 */ public void setGroups(String[] groups) { try { ((DiscoveryGroupManagement) dm).setGroups(groups); } catch (IOException e) { throw new RuntimeException( "Could not change groups: " + e.getMessage(), e); } try { takeSnapshot(); } catch (IOException e) { throw new RuntimeException( "Could not log change: " + e.getMessage(), e); } } /** * Get the list of locators of specific lookup services to join. * * @return the list of locators of specific lookup services to join */ public LookupLocator[] getLocators() { 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 */ public void addLocators(LookupLocator[] locators) { ((DiscoveryLocatorManagement) dm).addLocators(locators); try { takeSnapshot(); } catch (IOException e) { throw new RuntimeException( "Could not log change: " + e.getMessage(), e); } } /** * 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 */ public void removeLocators(LookupLocator[] locators) { ((DiscoveryLocatorManagement) dm).removeLocators(locators); try { takeSnapshot(); } catch (IOException e) { throw new RuntimeException( "Could not log change: " + e.getMessage(), e); } } /** * 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 */ public void setLocators(LookupLocator[] locators) { ((DiscoveryLocatorManagement) dm).setLocators(locators); try { takeSnapshot(); } catch (IOException e) { throw new RuntimeException( "Could not log change: " + e.getMessage(), e); } } /** * Get the current attribute sets for the service. * * @return the current attribute sets for the service */ public Entry[] getAttributes() { return joinMgr.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 * @param checkSC <code>boolean</code> flag indicating whether the * elements of the set of attributes to add should be * checked to determine if they are service controlled * @throws SecurityException when the <code>checkSC</code> parameter is * <code>true</code>, and at least one of the attributes to be * added is an instance of the <code>ServiceControlled</code> * marker interface */ public void addAttributes(Entry[] attrSets, boolean checkSC) { joinMgr.addAttributes(attrSets, checkSC); try { takeSnapshot(); } catch (IOException e) { throw new RuntimeException( "Could not log change: " + e.getMessage(), e); } } /** * 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 * @param checkSC <code>boolean</code> flag indicating whether * elements of the set of attributes to add should be checked * to determine if they are service controlled * @throws SecurityException when the <code>checkSC</code> parameter * is <code>true</code>, and at least one of the attributes * to be added is an instance of the * <code>ServiceControlled</code> marker interface * @see net.jini.core.lookup.ServiceRegistration#modifyAttributes */ public void modifyAttributes(Entry[] attrSetTemplates, Entry[] attrSets, boolean checkSC) { joinMgr.modifyAttributes(attrSetTemplates, attrSets, checkSC); try { takeSnapshot(); } catch (IOException e) { throw new RuntimeException( "Could not log change: " + e.getMessage(), e); } } }