/*
 * 
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * The contents of this file are subject to the Sun Community Source
 * License v3.0/Jini Technology Specific Attachment v 1.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://wwws.sun.com/software/jini/licensing/SCSL3_JiniTSA1.html.
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Reference Code is Jini Technology Core Platform code, v2.0. The
 * Developer of the Reference Code is Sun Microsystems, Inc.
 * 
 * Contributor(s): Sun Microsystems, Inc.
 * 
 * The contents of this file comply with the Jini Technology Core
 * Platform Compatibility Kit, v2.0A.
 * 
 * Tester(s): Sun Microsystems, Inc.
 * 
 * Test Platform(s):
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Solaris(TM)SPARC/x86
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Linux (Intel x86)
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Windows (Intel Platform)
 * 
 */

package net.jini.discovery;

import com.sun.jini.discovery.Discovery;
import com.sun.jini.discovery.DiscoveryConstraints;
import com.sun.jini.discovery.UnicastResponse;
import com.sun.jini.discovery.UnicastSocketTimeout;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import javax.security.auth.Subject;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.MethodConstraints;
import net.jini.core.constraint.RemoteMethodControl;
import net.jini.core.discovery.LookupLocator;
import net.jini.core.lookup.ServiceRegistrar;

/**
 * <code>LookupLocator</code> subclass which supports constraint operations
 * through the {@link RemoteMethodControl} interface.  The constraints of a
 * <code>ConstrainableLookupLocator</code> instance control how it performs
 * unicast discovery, and apply only to its {@link LookupLocator#getRegistrar()
 * getRegistrar()} and {@link LookupLocator#getRegistrar(int)
 * getRegistrar(int)} methods.  The constraints may also be used by other
 * utilities, such as {@link LookupLocatorDiscovery}, to determine how unicast
 * discovery should be performed on behalf of a given
 * <code>ConstrainableLookupLocator</code> instance.  Untrusted
 * <code>ConstrainableLookupLocator</code> instances can be verified using the
 * {@link ConstrainableLookupLocatorTrustVerifier} trust verifier.
 *
 * @author	Sun Microsystems, Inc.
 * @since 2.0
 *
 * @com.sun.jini.impl
 *
 * This class supports use of the following constraint types to control unicast
 * discovery behavior:
 * <ul>
 *   <li> {@link com.sun.jini.discovery.DiscoveryProtocolVersion}: this
 *        constraint can be used to control which version of the unicast
 *        discovery protocol is used.
 *   <li> {@link com.sun.jini.discovery.UnicastSocketTimeout}: this constraint
 *        can be used to control the read timeout set on sockets over which
 *        unicast discovery is performed.
 * </ul>
 * <p>
 * In addition, the {@link com.sun.jini.discovery.MulticastMaxPacketSize} and
 * {@link com.sun.jini.discovery.MulticastTimeToLive} constraint types are
 * trivially supported, but do not have any effect on unicast discovery
 * operations.  Constraints other than those mentioned above are passed on to
 * the underlying implementations of versions 1 and 2 of the discovery
 * protocols.
 * <p>
 * An example of using constraints with <code>ConstrainableLookupLocator</code>
 * is:
 * <blockquote><pre>
 * new ConstrainableLookupLocator("target_host", 4160, new {@link net.jini.constraint.BasicMethodConstraints}(
 *     new {@link InvocationConstraints}(
 *         DiscoveryProtocolVersion.TWO, new UnicastSocketTimeout(120000))));
 * </pre></blockquote>
 * The resulting <code>ConstrainableLookupLocator</code> instance would (when
 * used) perform unicast discovery to the host <code>target_host</code> on port
 * 4160 using discovery protocol version 2, with a socket read timeout of
 * 120000 milliseconds unless one was explicitly specified using the {@link
 * #getRegistrar(int)} method.
 */
public final class ConstrainableLookupLocator
    extends LookupLocator implements RemoteMethodControl
{
    private static final long serialVersionUID = 7061417093114347317L;

    private static final int DEFAULT_TIMEOUT = 60 * 1000;
    private static final Method getRegistrarMethod;
    private static final Method getRegistrarTimeoutMethod;
    static {
	try {
	    // REMIND: lookup methods on ConstrainableLookupLocator instead?
	    getRegistrarMethod = LookupLocator.class.getMethod(
		"getRegistrar", new Class[0]);
	    getRegistrarTimeoutMethod = LookupLocator.class.getMethod(
		"getRegistrar", new Class[]{ int.class });
	} catch (NoSuchMethodException e) {
	    throw new AssertionError(e);
	}
    }

    /**
     * The client-side method constraints for unicast discovery.
     * @serial
     */
    private final MethodConstraints constraints;

    /**
     * Constructs a new <code>ConstrainableLookupLocator</code> instance which
     * can be used to perform unicast discovery to the host and port named by
     * the given URL with the provided constraints applied.
     * <p>
     * The given URL must be of the form <tt>jini://host/</tt> or
     * <tt>jini://host:port/</tt>.  A <code>null constraints</code> value is
     * interpreted as mapping both <code>getRegistrar</code> methods to empty
     * constraints.
     *
     * @param url the URL to use
     * @param constraints the constraints to apply to unicast discovery, or
     * <code>null</code>
     * @throws MalformedURLException if the given URL cannot be parsed
     * @throws NullPointerException if the given URL is <code>null</code>
     */
    public ConstrainableLookupLocator(String url,
				      MethodConstraints constraints)
	throws MalformedURLException
    {
	super(url);
	this.constraints = constraints;
    }

    /**
     * Constructs a new <code>ConstrainableLookupLocator</code> instance which
     * can be used to perform unicast discovery to the given host and port with
     * the provided constraints applied.
     *
     * <p>A <code>null constraints</code> value is interpreted as mapping both
     * <code>getRegistrar</code> methods to empty constraints.
     *
     * @param host the name of the host to contact
     * @param port the number of the port to connect to
     * @param constraints the constraints to apply to unicast discovery, or
     * <code>null</code>
     * @throws NullPointerException if the host name is <code>null</code>
     * @throws IllegalArgumentException if the port number is out of range
     */
    public ConstrainableLookupLocator(String host,
				      int port,
				      MethodConstraints constraints)
    {
	super(host, port);
	this.constraints = constraints;
    }

    // documentation inherited from LookupLocator.getRegistrar
    public ServiceRegistrar getRegistrar()
	throws IOException, ClassNotFoundException
    {
	return getRegistrar((constraints != null) ?
	    constraints.getConstraints(getRegistrarMethod) :
	    InvocationConstraints.EMPTY);
    }

    /**
     * {@inheritDoc}
     *
     * The time limit is considered a requirement with respect to other
     * constraints specified for this instance.
     */
    public ServiceRegistrar getRegistrar(int timeout)
	throws IOException, ClassNotFoundException
    {
	InvocationConstraints ic = (constraints != null) ?
	    constraints.getConstraints(getRegistrarTimeoutMethod) :
	    InvocationConstraints.EMPTY;
	Collection reqs = new ArrayList(ic.requirements());
	reqs.add(new UnicastSocketTimeout(timeout));
	return getRegistrar(new InvocationConstraints(reqs, ic.preferences()));
    }

    // documentation inherited from RemoteMethodControl.setConstraints
    public RemoteMethodControl setConstraints(MethodConstraints constraints) {
	return new ConstrainableLookupLocator(host, port, constraints);
    }

    // documentation inherited from RemoteMethodControl.getConstraints
    public MethodConstraints getConstraints() {
	return constraints;
    }

    private ServiceRegistrar getRegistrar(InvocationConstraints constraints)
	throws IOException, ClassNotFoundException
    {
	DiscoveryConstraints dc = DiscoveryConstraints.process(constraints);
	int pv = dc.chooseProtocolVersion();
	Discovery disco;
	switch (pv) {
	    case Discovery.PROTOCOL_VERSION_1:
		disco = Discovery.getProtocol1();
		break;
	    case Discovery.PROTOCOL_VERSION_2:
		disco = Discovery.getProtocol2(null);
		break;
	    default:
		throw new AssertionError(pv);
	}

	Socket s = new Socket(host, port);
	s.setSoTimeout(dc.getUnicastSocketTimeout(DEFAULT_TIMEOUT));
	try {
	    UnicastResponse resp = disco.doUnicastDiscovery(
		s, dc.getUnfulfilledConstraints(), null, null, null);
	    return resp.getRegistrar();
	} finally {
	    try {
		s.close();
	    } catch (IOException e) {
	    }
	}
    }
}
