/* * 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.discovery; import java.lang.reflect.Method; import java.util.Collections; import java.util.Iterator; import java.util.Set; import java.util.HashSet; import net.jini.core.constraint.ConnectionAbsoluteTime; import net.jini.core.constraint.ConnectionRelativeTime; import net.jini.core.constraint.ConstraintAlternatives; import net.jini.core.constraint.InvocationConstraint; import net.jini.core.constraint.InvocationConstraints; import net.jini.core.constraint.MethodConstraints; import net.jini.io.UnsupportedConstraintException; /** * Class for processing constraints which apply to the discovery protocol: * {@link DiscoveryProtocolVersion}, {@link MulticastMaxPacketSize}, * {@link MulticastTimeToLive}, {@link UnicastSocketTimeout}, * {@link net.jini.core.constraint.ConnectionRelativeTime}, * {@link net.jini.core.constraint.ConnectionAbsoluteTime}. * * @author Sun Microsystems, Inc. * @since 2.0 */ public class DiscoveryConstraints { /** Method object for the multicastRequest method of this class. */ public static final Method multicastRequestMethod; /** Method object for the multicastAnnouncement method of this class. */ public static final Method multicastAnnouncementMethod; /** Method object for the unicastDiscovery method of this class. */ public static final Method unicastDiscoveryMethod; static { try { multicastRequestMethod = DiscoveryConstraints.class.getMethod( "multicastRequest", null); multicastAnnouncementMethod = DiscoveryConstraints.class.getMethod( "multicastAnnouncement", null); unicastDiscoveryMethod = DiscoveryConstraints.class.getMethod( "unicastDiscovery", null); } catch (NoSuchMethodException e) { throw new AssertionError(e); } } private static final Set supportedProtocols = new HashSet(2); static { supportedProtocols.add(DiscoveryProtocolVersion.ONE); supportedProtocols.add(DiscoveryProtocolVersion.TWO); } private final InvocationConstraints unfulfilled; private final Set protocolVersions; private final int preferredProtocolVersion; private final ConnectionAbsoluteTime connectionAbsoluteTime; private final MulticastMaxPacketSize maxPacketSize; private final MulticastTimeToLive timeToLive; private final UnicastSocketTimeout socketTimeout; /** * Empty method which serves as a {@link MethodConstraints} key for looking * up {@link InvocationConstraints} that apply to multicast requests. */ public static void multicastRequest() { } /** * Empty method which serves as a {@link MethodConstraints} key for looking * up {@link InvocationConstraints} that apply to multicast announcements. */ public static void multicastAnnouncement() { } /** * Empty method which serves as a {@link MethodConstraints} key for looking * up {@link InvocationConstraints} that apply to unicast discovery. */ public static void unicastDiscovery() { } /** * Processes the discovery-related constraints in the given set of * constraints, returning a <code>DiscoveryConstraints</code> instance from * which the constraint results can be queried. Processing of timeout * constraints is time dependent, and subsequent invocations of this * method with the same <code>InvocationConstraints</code> may result in * <code>DiscoveryConstraint</code> instances with different constraint * results. * * @param constraints the constraints to process * @return an instance representing processed constraints * @throws UnsupportedConstraintException if the discovery-related * constraints contain conflicts, or otherwise cannot be processed * @throws NullPointerException if <code>constraints</code> is * <code>null</code> * */ public static DiscoveryConstraints process( InvocationConstraints constraints) throws UnsupportedConstraintException { return new DiscoveryConstraints(constraints); } private DiscoveryConstraints(InvocationConstraints constraints) throws UnsupportedConstraintException { unfulfilled = new InvocationConstraints( getUnfulfilled(constraints.requirements()), getUnfulfilled(constraints.preferences())); ConstraintReducer cr = new ConstraintReducer(DiscoveryProtocolVersion.class); protocolVersions = cr.reduce( new InvocationConstraints(constraints.requirements(), null)); if (!protocolVersions.isEmpty() && intersect(protocolVersions, supportedProtocols).isEmpty()) { throw new UnsupportedConstraintException( "no supported protocols: " + protocolVersions); } preferredProtocolVersion = chooseProtocolVersion( protocolVersions, cr.reduce(constraints), unfulfilled); Set s = new MulticastMaxPacketSizeReducer().reduce(constraints); maxPacketSize = s.isEmpty() ? null : (MulticastMaxPacketSize) getElement(s); s = new ConstraintReducer( MulticastTimeToLive.class).reduce(constraints); timeToLive = s.isEmpty() ? null : (MulticastTimeToLive) getElement(s); s = new ConstraintReducer( UnicastSocketTimeout.class).reduce(constraints); socketTimeout = s.isEmpty() ? null : (UnicastSocketTimeout) getElement(s); InvocationConstraints absConstraints = new InvocationConstraints( constraints.requirements(), constraints.preferences()).makeAbsolute(); s = new ConnectionAbsoluteTimeReducer().reduce(absConstraints); connectionAbsoluteTime = s.isEmpty() ? null : (ConnectionAbsoluteTime) getElement(s); } /** * Returns the protocol version to use for sending multicast requests or * announcements, or initiating unicast discovery. * * @return the protocol version to use */ public int chooseProtocolVersion() { return preferredProtocolVersion; } /** * Checks the protocol version of an incoming multicast request, * announcement, or unicast discovery attempt, throwing an {@link * UnsupportedConstraintException} if handling of the given protocol does * not satisfy the constraints of this instance. * * @param version protocol version to check * @throws UnsupportedConstraintException if handling of the given protocol * does not satisfy the constraints of this instance */ public void checkProtocolVersion(int version) throws UnsupportedConstraintException { if (!(protocolVersions.isEmpty() || protocolVersions.contains( DiscoveryProtocolVersion.getInstance(version)))) { throw new UnsupportedConstraintException( "disallowed protocol: " + version); } } /** * Returns the deadline by which a network connection must be established * during unicast discovery, or <code>defaultValue</code> if not * constrained. * * @param defaultValue default timeout to return * @return the deadline for the network connection * @since 2.1 */ public long getConnectionDeadline(long defaultValue) { return connectionAbsoluteTime != null ? connectionAbsoluteTime.getTime() : defaultValue; } /** * Returns the maximum multicast packet size to allow, or the specified * default value if not constrained. * * @param defaultValue the value to return if the multicast packet size is * unconstrained * @return the maximum multicast packet size to allow */ public int getMulticastMaxPacketSize(int defaultValue) { return (maxPacketSize != null) ? maxPacketSize.getSize() : defaultValue; } /** * Returns the multicast time to live value to use, or the specified * default value if not constrained. * * @param defaultValue the value to return if the multicast time to live * value is unconstrained * @return the multicast time to live value to use */ public int getMulticastTimeToLive(int defaultValue) { return (timeToLive != null) ? timeToLive.getTimeToLive() : defaultValue; } /** * Returns socket read timeout to use for unicast discovery, or the * specified default value if not constrained. * * @param defaultValue the value to return if the socket timeout is * unconstrained * @return the socket timeout to use */ public int getUnicastSocketTimeout(int defaultValue) { return (socketTimeout != null) ? socketTimeout.getTimeout() : defaultValue; } /** * Returns the constraints for this instance which are not, or do not * contain as alternatives, instances of the "fulfillable" (by this layer) * constraint types <code>DiscoveryProtocolVersion</code>, * <code>ConnectionRelativeTime</code>, <code>MulticastMaxPacketSize</code>, * <code>MulticastTimeToLive</code>, and <code>UnicastSocketTimeout</code>. * Constraint alternatives containing both fulfillable and unfulfillable * constraints are treated optimistically--it is assumed that the one of the * fulfillable alternatives will be satisfied, so the unfulfillable * alternatives are not included in the returned constraints. * * @return the unfulfilled constraints for this instance */ public InvocationConstraints getUnfulfilledConstraints() { return unfulfilled; } /** * Utility for reducing constraints of a given type into a base set of * alternatives. For each type of constraints that it reduces, this class * makes the simplifying assumption that failure to reduce constraints of * that type into a single (perhaps one element) set of alternatives * represents a constraint conflict--i.e., that two disjoint instances of * the constraint type cannot be applied to the same operation. While this * restriction does not hold across all possible constraints, it is * satisfied by the particular constraint types that DiscoveryConstraints * handles. * * If this utility encounters a set of alternatives containing instances of * both the type to reduce as well as other constraint types, then it * ignores the instances of the other types, treating the set as if it can * only be satisfied by the alternatives of the targeted type. This may in * some cases cause false positives when detecting conflicts in constraints * containing alternatives of mixed type; however, it should never result * in false negatives. */ private static class ConstraintReducer { private final Class targetClass; /** * Creates reducer that operates on instances of the given constraint * class. */ ConstraintReducer(Class targetClass) { this.targetClass = targetClass; } /** * Returns the reduction of the given constraints into a single set of * alternatives for the target class. Returns an empty set if no * constraints of the target class are specified. Throws * UnsupportedConstraintException if the constraints conflict. */ Set reduce(InvocationConstraints constraints) throws UnsupportedConstraintException { Set reduced = reduce(null, constraints.requirements(), true); reduced = reduce(reduced, constraints.preferences(), false); return (reduced != null) ? reduced : Collections.EMPTY_SET; } /** * Returns the reduction (intersection and compaction) of a new set of * alternative constraints, all instances of the target class, with a * previously reduced set (null if no other constraints have been * reduced yet). Returns an empty set if elements in the sets * conflict. This method can be overridden by subclasses for * constraints with particular reduction semantics; the default * implementation of this method returns the intersection of the two * sets. */ Set reduce0(Set reduced, Set toReduce) { return (reduced != null) ? intersect(reduced, toReduce) : toReduce; } private Set reduce(Set reduced, Set constraints, boolean required) throws UnsupportedConstraintException { for (Iterator i = constraints.iterator(); i.hasNext(); ) { InvocationConstraint c = (InvocationConstraint) i.next(); Set toReduce = Collections.EMPTY_SET; if (targetClass.isInstance(c)) { toReduce = Collections.singleton(c); } else if (c instanceof ConstraintAlternatives) { toReduce = getTargetInstances( ((ConstraintAlternatives) c).elements()); } if (!toReduce.isEmpty()) { Set s = reduce0(reduced, toReduce); if (!s.isEmpty()) { reduced = s; } else if (required) { throw new UnsupportedConstraintException( "constraints conflict: " + constraints); } } } return reduced; } private Set getTargetInstances(Set set) { Set instances = Collections.EMPTY_SET; for (Iterator i = set.iterator(); i.hasNext(); ) { Object obj = i.next(); if (targetClass.isInstance(obj)) { if (instances.isEmpty()) { instances = new HashSet(); } instances.add(obj); } } return instances; } } /* * Abstract class which reduces constraints that are specified to have * a max value. Subclasses only have to handle returning the value of the * constraint and creating a constraint given an input value. */ private abstract static class MaxValueReducer extends ConstraintReducer { MaxValueReducer(Class targetClass) { super(targetClass); } abstract long getValue(InvocationConstraint ic); abstract InvocationConstraint getConstraintInstance(long value); /* * Reduces the alternatives in toReduce to the maximum value and then * chooses the minimum between this value and the one from reduced. * Invokes abstract method getValue to allow subclasses to return the * value for specific constraint classes and invokes * getConstraintInstance to finally return the actual instance given * the reduced value that has been determined as above. */ Set reduce0(Set reduced, Set toReduce) { long value = 0; for (Iterator i = toReduce.iterator(); i.hasNext(); ) { value = Math.max( value, getValue((InvocationConstraint) i.next())); } if (reduced != null) { value = Math.min( value, getValue((InvocationConstraint) getElement(reduced))); } return Collections.singleton(getConstraintInstance(value)); } } private static class MulticastMaxPacketSizeReducer extends MaxValueReducer { MulticastMaxPacketSizeReducer() { super(MulticastMaxPacketSize.class); } long getValue(InvocationConstraint maxPacketSize) { return ((MulticastMaxPacketSize) maxPacketSize).getSize(); } InvocationConstraint getConstraintInstance(long value) { if (value > Integer.MAX_VALUE) { // Shouldnt really happen as we are only dealing with // MulticasatMaxPacketSize constraints which are int throw new AssertionError("Value too large " + value); } return new MulticastMaxPacketSize((int)value); } } private static class ConnectionAbsoluteTimeReducer extends MaxValueReducer { ConnectionAbsoluteTimeReducer() { super(ConnectionAbsoluteTime.class); } long getValue(InvocationConstraint absTime) { return ((ConnectionAbsoluteTime) absTime).getTime(); } InvocationConstraint getConstraintInstance(long value) { return new ConnectionAbsoluteTime(value); } } private static Set getUnfulfilled(Set constraints) { Set unfulfilled = new HashSet(constraints.size()); for (Iterator i = constraints.iterator(); i.hasNext(); ) { InvocationConstraint c = (InvocationConstraint) i.next(); if (c instanceof ConstraintAlternatives) { Set s = ((ConstraintAlternatives) c).elements(); Set u = getUnfulfilled(s); if (u.size() == s.size()) { unfulfilled.add(c); } } else if (!(c instanceof DiscoveryProtocolVersion || c instanceof MulticastMaxPacketSize || c instanceof MulticastTimeToLive || c instanceof UnicastSocketTimeout || c instanceof ConnectionAbsoluteTime || c instanceof ConnectionRelativeTime)) { unfulfilled.add(c); } } return unfulfilled; } private static int chooseProtocolVersion(Set protocolVersions, Set protocolVersionPrefs, InvocationConstraints unfulfilled) { DiscoveryProtocolVersion bias = unfulfilled.isEmpty() ? DiscoveryProtocolVersion.ONE : DiscoveryProtocolVersion.TWO; Set[] sets = { protocolVersionPrefs, protocolVersions }; for (int i = 0; i < sets.length; i++) { Set s = sets[i]; if (s.contains(bias)) { return bias.getVersion(); } if (!(s = intersect(s, supportedProtocols)).isEmpty()) { return ((DiscoveryProtocolVersion) getElement(s)).getVersion(); } } return bias.getVersion(); } private static Set intersect(Set s1, Set s2) { Set intersection = Collections.EMPTY_SET; for (Iterator i = s1.iterator(); i.hasNext(); ) { Object obj = i.next(); if (s2.contains(obj)) { if (intersection.isEmpty()) { intersection = new HashSet(); } intersection.add(obj); } } return intersection; } private static Object getElement(Set s) { return s.iterator().next(); } }