View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    * 
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.river.discovery;
20  
21  import java.lang.reflect.Method;
22  import java.util.Collections;
23  import java.util.Iterator;
24  import java.util.Set;
25  import java.util.HashSet;
26  import net.jini.core.constraint.ConnectionAbsoluteTime;
27  import net.jini.core.constraint.ConnectionRelativeTime;
28  import net.jini.core.constraint.ConstraintAlternatives;
29  import net.jini.core.constraint.InvocationConstraint;
30  import net.jini.core.constraint.InvocationConstraints;
31  import net.jini.core.constraint.MethodConstraints;
32  import net.jini.io.UnsupportedConstraintException;
33  
34  /**
35   * Class for processing constraints which apply to the discovery protocol:
36   * {@link DiscoveryProtocolVersion}, {@link MulticastMaxPacketSize},
37   * {@link MulticastTimeToLive}, {@link UnicastSocketTimeout},
38   * {@link net.jini.core.constraint.ConnectionRelativeTime},
39   * {@link net.jini.core.constraint.ConnectionAbsoluteTime}.
40   *
41   * @author Sun Microsystems, Inc.
42   * @since 2.0
43   */
44  public class DiscoveryConstraints {
45  
46      /** Method object for the multicastRequest method of this class. */
47      public static final Method multicastRequestMethod;
48      /** Method object for the multicastAnnouncement method of this class. */
49      public static final Method multicastAnnouncementMethod;
50      /** Method object for the unicastDiscovery method of this class. */
51      public static final Method unicastDiscoveryMethod;
52      static {
53  	try {
54  	    multicastRequestMethod = DiscoveryConstraints.class.getMethod(
55  		"multicastRequest", (Class<?>[]) null);
56  	    multicastAnnouncementMethod = DiscoveryConstraints.class.getMethod(
57  		"multicastAnnouncement", (Class<?>[]) null);
58  	    unicastDiscoveryMethod = DiscoveryConstraints.class.getMethod(
59  		"unicastDiscovery", (Class<?>[]) null);
60  	} catch (NoSuchMethodException e) {
61  	    throw new AssertionError(e);
62  	}
63      }
64  
65      private static final Set<DiscoveryProtocolVersion> supportedProtocols = new HashSet<DiscoveryProtocolVersion>(2);
66      static {
67  	supportedProtocols.add(DiscoveryProtocolVersion.ONE);
68  	supportedProtocols.add(DiscoveryProtocolVersion.TWO);
69      }
70  
71      private final InvocationConstraints unfulfilled;
72      private final Set<DiscoveryProtocolVersion> protocolVersions;
73      private final int preferredProtocolVersion;
74      private final ConnectionAbsoluteTime connectionAbsoluteTime;
75      private final MulticastMaxPacketSize maxPacketSize;
76      private final MulticastTimeToLive timeToLive;
77      private final UnicastSocketTimeout socketTimeout;
78      private final int hashcode;
79  
80      @Override
81      public int hashCode() {
82  	return hashcode;
83      }
84      
85      @Override
86      public boolean equals(Object o){
87          if ( o == null ) return false;
88  	if ( o == this ) return true;
89  	if ( o.hashCode() != hashcode) return false;
90  	if ( o instanceof DiscoveryConstraints) {
91  	    DiscoveryConstraints that = (DiscoveryConstraints) o;
92  	    if ( unfulfilled != null ) {
93  		if ( !unfulfilled.equals(that.unfulfilled) ) return false;
94  	    } else if ( unfulfilled != that.unfulfilled) return false;
95  	    if (  protocolVersions != null ) {
96  		if ( !protocolVersions.equals(that.protocolVersions) ) return false;
97  	    } else if ( protocolVersions!= that.protocolVersions) return false;
98  	    if (  connectionAbsoluteTime != null ) {
99  		if ( !connectionAbsoluteTime.equals(that.connectionAbsoluteTime) ) return false;
100 	    } else if ( connectionAbsoluteTime != that.connectionAbsoluteTime) return false;
101 	    if (preferredProtocolVersion != that.preferredProtocolVersion) return false;
102 	    if ( maxPacketSize  != null ) {
103 		if ( !maxPacketSize.equals(that.maxPacketSize) ) return false;
104 	    } else if ( maxPacketSize != that.maxPacketSize) return false;    
105 	    if (  timeToLive != null ) {
106 		if ( !timeToLive.equals(that.timeToLive) ) return false;
107 	    } else if ( timeToLive != that.timeToLive) return false;
108 	    if (  socketTimeout != null ) {
109 		if ( !socketTimeout.equals(that.socketTimeout) ) return false;
110 	    } else if ( socketTimeout != that.socketTimeout) return false;    		
111 	    return true;	    
112 	}
113 	return false;
114     }
115 
116     /**
117      * Empty method which serves as a {@link MethodConstraints} key for looking
118      * up {@link InvocationConstraints} that apply to multicast requests.
119      */
120     public static void multicastRequest() {
121     }
122 
123     /**
124      * Empty method which serves as a {@link MethodConstraints} key for looking
125      * up {@link InvocationConstraints} that apply to multicast announcements.
126      */
127     public static void multicastAnnouncement() {
128     }
129 
130     /**
131      * Empty method which serves as a {@link MethodConstraints} key for looking
132      * up {@link InvocationConstraints} that apply to unicast discovery.
133      */
134     public static void unicastDiscovery() {
135     }
136 
137     /**
138      * Processes the discovery-related constraints in the given set of
139      * constraints, returning a <code>DiscoveryConstraints</code> instance from
140      * which the constraint results can be queried.  Processing of timeout
141      * constraints is time dependent, and subsequent invocations of this
142      * method with the same <code>InvocationConstraints</code> may result in
143      * <code>DiscoveryConstraint</code> instances with different constraint
144      * results.
145      *
146      * @param constraints the constraints to process
147      * @return an instance representing processed constraints
148      * @throws UnsupportedConstraintException if the discovery-related
149      * constraints contain conflicts, or otherwise cannot be processed
150      * @throws NullPointerException if <code>constraints</code> is
151      * <code>null</code>
152      *
153      */
154     public static DiscoveryConstraints process(
155 					   InvocationConstraints constraints)
156 	throws UnsupportedConstraintException
157     {
158 	return new DiscoveryConstraints(constraints);
159     }
160 
161     private DiscoveryConstraints(InvocationConstraints constraints)
162 	throws UnsupportedConstraintException
163     {
164 	unfulfilled = new InvocationConstraints(
165 	    getUnfulfilled(constraints.requirements()),
166 	    getUnfulfilled(constraints.preferences()));
167 
168 	ConstraintReducer<DiscoveryProtocolVersion> cr = 
169 	    new ConstraintReducer<DiscoveryProtocolVersion>(DiscoveryProtocolVersion.class);
170 	protocolVersions = cr.reduce(
171 	    new InvocationConstraints(constraints.requirements(), null));
172 	if (!protocolVersions.isEmpty() &&
173 	    intersect(protocolVersions, supportedProtocols).isEmpty())
174 	{
175 	    throw new UnsupportedConstraintException(
176 		"no supported protocols: " + protocolVersions);
177 	}
178 	preferredProtocolVersion = chooseProtocolVersion(
179 	    protocolVersions, cr.reduce(constraints), unfulfilled);
180 	Set<MulticastMaxPacketSize> mmps 
181 	    = new MulticastMaxPacketSizeReducer().reduce(constraints);
182 	maxPacketSize = mmps.isEmpty() ? null : getElement(mmps);
183 	Set<MulticastTimeToLive> mttl 
184 	    = new ConstraintReducer<MulticastTimeToLive>(
185 	    MulticastTimeToLive.class).reduce(constraints);
186 	timeToLive = mttl.isEmpty() ? null : getElement(mttl);
187 	Set<UnicastSocketTimeout> ust 
188 	    = new ConstraintReducer<UnicastSocketTimeout>(
189 	    UnicastSocketTimeout.class).reduce(constraints);
190 	socketTimeout = ust.isEmpty() ? null : getElement(ust);
191 	InvocationConstraints absConstraints 
192 	    = new InvocationConstraints(
193 		constraints.requirements(),
194 		constraints.preferences()).makeAbsolute();
195 	Set<ConnectionAbsoluteTime> cat 
196 		= new ConnectionAbsoluteTimeReducer().reduce(absConstraints);
197 	connectionAbsoluteTime = cat.isEmpty() ? null : getElement(cat);
198 	int hash = 7;
199 	hash = 41 * hash + this.unfulfilled.hashCode();
200 	hash = 41 * hash + this.protocolVersions.hashCode();
201 	hash = 41 * hash + this.preferredProtocolVersion;
202 	hash = 41 * hash + (this.connectionAbsoluteTime != null ? this.connectionAbsoluteTime.hashCode() : 0);
203 	hash = 41 * hash + (this.maxPacketSize != null ? this.maxPacketSize.hashCode() : 0);
204 	hash = 41 * hash + (this.timeToLive != null ? this.timeToLive.hashCode() : 0);
205 	hash = 41 * hash + (this.socketTimeout != null ? this.socketTimeout.hashCode() : 0);
206 	hashcode = hash;
207     }
208 
209     /**
210      * Returns the protocol version to use for sending multicast requests or
211      * announcements, or initiating unicast discovery.
212      *
213      * @return the protocol version to use
214      */
215     public int chooseProtocolVersion() {
216 	return preferredProtocolVersion;
217     }
218 
219     /**
220      * Checks the protocol version of an incoming multicast request,
221      * announcement, or unicast discovery attempt, throwing an {@link
222      * UnsupportedConstraintException} if handling of the given protocol does
223      * not satisfy the constraints of this instance.
224      *
225      * @param version protocol version to check
226      * @throws UnsupportedConstraintException if handling of the given protocol
227      * does not satisfy the constraints of this instance
228      */
229     public void checkProtocolVersion(int version)
230 	throws UnsupportedConstraintException
231     {
232 	if (!(protocolVersions.isEmpty() ||
233 	      protocolVersions.contains(
234 		  DiscoveryProtocolVersion.getInstance(version))))
235 	{
236 	    throw new UnsupportedConstraintException(
237 		"disallowed protocol: " + version);
238 	}
239     }
240     
241     /**
242      * Returns the deadline by which a network connection must be established
243      * during unicast discovery, or <code>defaultValue</code> if not
244      * constrained.
245      * 
246      * @param defaultValue default timeout to return
247      * @return the deadline for the network connection
248      * @since 2.1
249      */
250     public long getConnectionDeadline(long defaultValue) {
251 	return connectionAbsoluteTime != null ? 
252 	    connectionAbsoluteTime.getTime() : defaultValue;
253     }
254 
255     /**
256      * Returns the maximum multicast packet size to allow, or the specified
257      * default value if not constrained.
258      *
259      * @param defaultValue the value to return if the multicast packet size is
260      * unconstrained
261      * @return the maximum multicast packet size to allow
262      */
263     public int getMulticastMaxPacketSize(int defaultValue) {
264 	return (maxPacketSize != null) ? 
265 	    maxPacketSize.getSize() : defaultValue;
266     }
267 
268     /**
269      * Returns the multicast time to live value to use, or the specified
270      * default value if not constrained.
271      *
272      * @param defaultValue the value to return if the multicast time to live
273      * value is unconstrained
274      * @return the multicast time to live value to use
275      */
276     public int getMulticastTimeToLive(int defaultValue) {
277 	return (timeToLive != null) ?
278 	    timeToLive.getTimeToLive() : defaultValue;
279     }
280 
281     /**
282      * Returns socket read timeout to use for unicast discovery, or the 
283      * specified default value if not constrained.
284      *
285      * @param defaultValue the value to return if the socket timeout is
286      * unconstrained
287      * @return the socket timeout to use
288      */
289     public int getUnicastSocketTimeout(int defaultValue) {
290 	return (socketTimeout != null) ? 
291 	    socketTimeout.getTimeout() : defaultValue;
292     }
293 
294     /**
295      * Returns the constraints for this instance which are not, or do not
296      * contain as alternatives, instances of the "fulfillable" (by this layer)
297      * constraint types <code>DiscoveryProtocolVersion</code>, 
298      * <code>ConnectionRelativeTime</code>, <code>MulticastMaxPacketSize</code>, 
299      * <code>MulticastTimeToLive</code>, and <code>UnicastSocketTimeout</code>.  
300      * Constraint alternatives containing both fulfillable and unfulfillable 
301      * constraints are treated optimistically--it is assumed that the one of the 
302      * fulfillable alternatives will be satisfied, so the unfulfillable 
303      * alternatives are not included in the returned constraints.
304      *
305      * @return the unfulfilled constraints for this instance
306      */
307     public InvocationConstraints getUnfulfilledConstraints() {
308 	return unfulfilled;
309     }
310 
311     /**
312      * Utility for reducing constraints of a given type into a base set of
313      * alternatives.  For each type of constraints that it reduces, this class
314      * makes the simplifying assumption that failure to reduce constraints of
315      * that type into a single (perhaps one element) set of alternatives
316      * represents a constraint conflict--i.e., that two disjoint instances of
317      * the constraint type cannot be applied to the same operation.  While this
318      * restriction does not hold across all possible constraints, it is
319      * satisfied by the particular constraint types that DiscoveryConstraints
320      * handles.
321      *
322      * If this utility encounters a set of alternatives containing instances of
323      * both the type to reduce as well as other constraint types, then it
324      * ignores the instances of the other types, treating the set as if it can
325      * only be satisfied by the alternatives of the targeted type.  This may in
326      * some cases cause false positives when detecting conflicts in constraints
327      * containing alternatives of mixed type; however, it should never result
328      * in false negatives.
329      */
330     private static class ConstraintReducer<T extends InvocationConstraint> {
331 
332 	private final Class<T> targetClass;
333 
334 	/**
335 	 * Creates reducer that operates on instances of the given constraint
336 	 * class.
337 	 */
338 	ConstraintReducer(Class<T> targetClass) {
339 	    this.targetClass = targetClass;
340 	}
341 	
342 	/**
343 	 * Returns the reduction of the given constraints into a single set of
344 	 * alternatives for the target class.  Returns an empty set if no
345 	 * constraints of the target class are specified.  Throws
346 	 * UnsupportedConstraintException if the constraints conflict.
347 	 */
348 	@SuppressWarnings("unchecked")
349 	Set<T> reduce(InvocationConstraints constraints)
350 	    throws UnsupportedConstraintException
351 	{
352 	    Set<T> reduced = reduce(null, constraints.requirements(), true);
353 	    reduced = reduce(reduced, constraints.preferences(), false);
354 	    return (reduced != null) ? reduced : Collections.EMPTY_SET;
355 	}
356 
357 	/**
358 	 * Returns the reduction (intersection and compaction) of a new set of
359 	 * alternative constraints, all instances of the target class, with a
360 	 * previously reduced set (null if no other constraints have been
361 	 * reduced yet).  Returns an empty set if elements in the sets
362 	 * conflict.  This method can be overridden by subclasses for
363 	 * constraints with particular reduction semantics; the default
364 	 * implementation of this method returns the intersection of the two
365 	 * sets.
366 	 */
367 	Set<T> reduce0(Set<T> reduced, Set<T> toReduce) {
368 	    return (reduced != null) ? intersect(reduced, toReduce) : toReduce;
369 	}
370 
371 	@SuppressWarnings("unchecked")
372 	private Set<T> reduce(Set<T> reduced, Set<InvocationConstraint> constraints, boolean required)
373 	    throws UnsupportedConstraintException
374 	{
375 	    for (Iterator<InvocationConstraint> i = constraints.iterator(); i.hasNext(); ) {
376 		InvocationConstraint c = i.next();
377 		Set<T> toReduce = Collections.EMPTY_SET;
378 		if (targetClass.isInstance(c)) {
379 		    toReduce = Collections.singleton((T) c);
380 		} else if (c instanceof ConstraintAlternatives) {
381 		    toReduce = getTargetInstances(
382 			((ConstraintAlternatives) c).elements());
383 		}
384 		if (!toReduce.isEmpty()) {
385 		    Set<T> s = reduce0(reduced, toReduce);
386 		    if (!s.isEmpty()) {
387 			reduced = s;
388 		    } else if (required) {
389 			throw new UnsupportedConstraintException(
390 			    "constraints conflict: " + constraints);
391 		    }
392 		}
393 	    }
394 	    return reduced;
395 	}
396 
397 	@SuppressWarnings("unchecked")
398 	private Set<T> getTargetInstances(Set<InvocationConstraint> set) {
399 	    Set<T> instances = Collections.EMPTY_SET;
400 	    for (Iterator<InvocationConstraint> i = set.iterator(); i.hasNext(); ) {
401 		InvocationConstraint obj = i.next();
402 		if (targetClass.isInstance(obj)) {
403 		    if (instances.isEmpty()) {
404 			instances = new HashSet<T>();
405 		    }
406 		    instances.add((T) obj);
407 		}
408 	    }
409 	    return instances;
410 	}
411     }
412 
413     /*
414      * Abstract class which reduces constraints that are specified to have
415      * a max value. Subclasses only have to handle returning the value of the
416      * constraint and creating a constraint given an input value.
417      */
418     private abstract static class MaxValueReducer<T extends InvocationConstraint> 
419 	extends ConstraintReducer<T>
420     {
421 	MaxValueReducer(Class<T> targetClass) {
422 	    super(targetClass);
423 	}
424 
425 	abstract long getValue(InvocationConstraint ic);
426 	abstract T getConstraintInstance(long value);
427 	
428 	/*
429 	 * Reduces the alternatives in toReduce to the maximum value and then
430 	 * chooses the minimum between this value and the one from reduced.
431 	 * Invokes abstract method getValue to allow subclasses to return the
432 	 * value for specific constraint classes and invokes
433 	 * getConstraintInstance to finally return the actual instance given
434 	 * the reduced value that has been determined as above.
435 	 */
436 	@Override
437 	Set<T> reduce0(Set<T> reduced, Set<T> toReduce) {
438 	    long value = 0;
439 	    for (Iterator<T> i = toReduce.iterator(); i.hasNext(); ) {
440 		value = Math.max(
441 		    value, getValue( i.next()));
442 	    }
443 	    if (reduced != null) {
444 		value = Math.min(
445 		    value,
446 		    getValue( getElement(reduced)));
447 	    }
448 	    return Collections.singleton((T) getConstraintInstance(value));
449 	}
450     }
451     
452     private static class MulticastMaxPacketSizeReducer 
453 	extends MaxValueReducer<MulticastMaxPacketSize>
454     {
455 	MulticastMaxPacketSizeReducer() {
456 	    super(MulticastMaxPacketSize.class);
457 	}
458 	
459 	@Override
460 	long getValue(InvocationConstraint maxPacketSize) {
461 	    return ((MulticastMaxPacketSize) maxPacketSize).getSize();
462 	}
463 	
464 	@Override
465 	MulticastMaxPacketSize getConstraintInstance(long value) {
466 	    if (value > Integer.MAX_VALUE) {
467 		// Shouldnt really happen as we are only dealing with
468 		// MulticasatMaxPacketSize constraints which are int
469 		throw new AssertionError("Value too large " + value);
470 	    }
471 	    return new MulticastMaxPacketSize((int)value);
472 	}
473     }
474         
475     private static class ConnectionAbsoluteTimeReducer extends MaxValueReducer<ConnectionAbsoluteTime> {
476 	ConnectionAbsoluteTimeReducer() {
477 	    super(ConnectionAbsoluteTime.class);
478 	}
479 	
480 	@Override
481 	long getValue(InvocationConstraint absTime) {
482 	    return ((ConnectionAbsoluteTime) absTime).getTime();
483 	}
484 	
485 	@Override
486 	ConnectionAbsoluteTime getConstraintInstance(long value) {
487 	    return new ConnectionAbsoluteTime(value);
488 	}
489     }
490 
491     private static Set<InvocationConstraint> getUnfulfilled(Set<InvocationConstraint> constraints) {
492 	Set<InvocationConstraint> unfulfilled = new HashSet<InvocationConstraint>(constraints.size());
493 	for (Iterator<InvocationConstraint> i = constraints.iterator(); i.hasNext(); ) {
494 	    InvocationConstraint c = i.next();
495 	    if (c instanceof ConstraintAlternatives) {
496 		Set<InvocationConstraint> s = ((ConstraintAlternatives) c).elements();
497 		Set<InvocationConstraint> u = getUnfulfilled(s);
498 		if (u.size() == s.size()) {
499 		    unfulfilled.add(c);
500 		}
501 	    } else if (!(c instanceof DiscoveryProtocolVersion ||
502 			 c instanceof MulticastMaxPacketSize ||
503 			 c instanceof MulticastTimeToLive ||
504 			 c instanceof UnicastSocketTimeout ||
505 			 c instanceof ConnectionAbsoluteTime ||
506 			 c instanceof ConnectionRelativeTime))
507 	    {
508 		unfulfilled.add(c);
509 	    }
510 	}
511 	return unfulfilled;
512     }
513 
514     private static int chooseProtocolVersion(Set<DiscoveryProtocolVersion> protocolVersions,
515 					     Set<DiscoveryProtocolVersion> protocolVersionPrefs,
516 					     InvocationConstraints unfulfilled)
517     {
518 	DiscoveryProtocolVersion bias = DiscoveryProtocolVersion.TWO; // One is deprecated.
519 	Set[] sets = { protocolVersionPrefs, protocolVersions };
520 	for (int i = 0, l = sets.length; i < l; i++) {
521 	    @SuppressWarnings("unchecked")
522 	    Set<DiscoveryProtocolVersion> s = sets[i];
523 	    if (s.contains(bias)) {
524 		return bias.getVersion();
525 	    }
526 	    if (!(s = intersect(s, supportedProtocols)).isEmpty()) {
527 		return ( getElement(s)).getVersion();
528 	    }
529 	}
530 	return bias.getVersion();
531     }
532 
533     private static <T> Set<T> intersect(Set<T> s1, Set s2) {
534 	@SuppressWarnings("unchecked")
535 	Set<T> intersection = Collections.EMPTY_SET;
536 	for (Iterator<T> i = s1.iterator(); i.hasNext(); ) {
537 	    T obj = i.next();
538 	    if (s2.contains(obj)) {
539 		if (intersection.isEmpty()) {
540 		    intersection = new HashSet<T>();
541 		}
542 		intersection.add(obj);
543 	    }
544 	}
545 	return intersection;
546     }
547 
548     private static <T> T getElement(Set<T> s) {
549 	return s.iterator().next();
550     }
551 }