/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright (c) 1999 by Sun Microsystems, Inc.
 * All rights reserved.
 *
 */

//  UARequester.java: Requester operations for UA.
//  Author:           James Kempf
//  Created On:       Thu Jan  8 15:17:35 1998
//  Last Modified By: James Kempf
//  Last Modified On: Mon Feb 22 13:47:06 1999
//  Update Count:     78
//

package com.sun.slp;

import java.util.*;

/**
 * The URequester class implements the Locator interface.
 * It handles the request for the API.  If any of the parameters
 * are missing, they will be supplied with a default value if
 * possible.  If a cached value may be supplied, it will be.
 * If no DA is present, and convergence is used to gather
 * results, these will be merged into one result.
 *
 * @author Erik Guttman, James Kempf
 */


class UARequester extends Object implements Locator {

    private static SLPConfig config = null;
    private static DATable dat = null;

    private Locale locale;

    UARequester(Locale nlocale) {

	Assert.nonNullParameter(nlocale, "locale");

	if (config == null) {
	    config = SLPConfig.getSLPConfig();
	}

	if (dat == null) {
	    dat = DATable.getDATable();
	}

	locale = nlocale;
    }

    /**
     * Return the Locator's locale object. All requests are made in
     * this locale.
     *
     * @return The Locale object.
     */

    public Locale getLocale() {
	return locale;

    }

    /**
     * Return an enumeration of known service types for this scope and naming
     * authority.  Unless a proprietary or experimental service is being
     * discovered, the namingAuthority parameter should be the empty
     * string, "".
     *
     * @param NA	The naming authority, "" for default,
     *           '*' for any naming authority.
     * @param scopes	The SLP scopes of the types.
     * @return ServiceLocationEnumeration of ServiceType objects for
     *	      the service type names.
     * @exception IllegalArgumentException If any of the parameters are
     *					  null or syntactically incorrect.
     * @exception ServiceLocationException An exception is thrown if the
     *					  operation fails.
     */

    public synchronized ServiceLocationEnumeration
	findServiceTypes(String NA, Vector scopes)
	throws ServiceLocationException {

	Assert.nonNullParameter(NA, " NA");
	Assert.nonNullParameter(scopes, "scopes");

	// Formulate and send off messages.

	Vector msgs = createMessages(SrvLocHeader.SrvTypeRqst,
				     NA,
				     null,
				     null,
				     scopes);

	// Collate results.

	Vector ret = new Vector();
	int i, n = msgs.size();
	int max = config.getMaximumResults();

	for (i = 0; i < n; i++) {
	    CSrvTypeMsg msg = (CSrvTypeMsg)msgs.elementAt(i);

	    // Check for errors.

	    checkForError(msg, msgs);

	    Vector serviceTypes = msg.serviceTypes;

	    addUnique(serviceTypes, ret, max);

	}

	// Return.

	return new ServiceLocationEnumerator(ret);
    }

    /**
     * Return an enumeration of ServiceURL objects for services matching
     * the query. The services are returned from the locale of the
     * locator.
     *
     * @param type	The type of the service (e.g. printer, etc.).
     * @param scopes	The SLP scopes of the service types.
     * @param query		A string with the SLP query.
     * @return ServiceLocationEnumeration of ServiceURL objects for
     *	      services matching the
     *         attributes.
     * @exception ServiceLocationException An exception is returned if the
     *					  operation fails.
     * @see ServiceURL
     */

    public synchronized ServiceLocationEnumeration
	findServices(ServiceType type, Vector scopes, String query)
	throws ServiceLocationException {

	Assert.nonNullParameter(type, "type");
	Assert.nonNullParameter(scopes, "scopes");
	Assert.nonNullParameter(query, "query");

	// Formulate and send off messages.

	Vector msgs = createMessages(SrvLocHeader.SrvReq,
				     type,
				     query,
				     type,
				     scopes);

	// Collate results.

	Vector ret = new Vector();
	int i, n = msgs.size();
	int max = config.getMaximumResults();

	for (i = 0; i < n; i++) {
	    SrvLocMsg msg = (SrvLocMsg)msgs.elementAt(i);

	    // Check for errors.

	    checkForError(msg, msgs);

	    // Be sure to account for DAAdverts and SAAdverts.

	    Vector serviceURLs = null;

	    if (msg instanceof CSrvMsg) {
		serviceURLs = ((CSrvMsg)msg).serviceURLs;

	    } else if (msg instanceof CSAAdvert) {
		serviceURLs = new Vector();
		serviceURLs.addElement(((CSAAdvert)msg).URL);

	    } else if (msg instanceof CDAAdvert) {
		serviceURLs = new Vector();
		serviceURLs.addElement(((CDAAdvert)msg).URL);

	    }

	    addUnique(serviceURLs, ret, max);

	}

	// Return.

	return new ServiceLocationEnumerator(ret);
    }

    /**
     * Return the attributes for the service URL, using the locale
     * of the locator.
     *
     * @param URL	The service URL.
     * @param scopes	The SLP scopes of the service.
     * @param attributeIds A vector of strings identifying the desired
     *			  attributes. A null value means return all
     *			  the attributes.  <b>Partial id strings</b> may
     *                     begin with '*' to match all ids which end with
     *                     the given suffix, or end with '*' to match all
     *                     ids which begin with a given prefix, or begin
     *                     and end with '*' to do substring matching for
     *                     ids containing the given partial id.
     * @return ServiceLocationEnumeration of ServiceLocationAttribute
     *         objects matching the ids.
     * @exception ServiceLocationException An exception is returned if the
     *					  operation fails.
     * @exception IllegalArgumentException If any of the parameters are
     *					  null or syntactically incorrect.
     * @see ServiceLocationAttribute
     *
     */

    public synchronized ServiceLocationEnumeration
	findAttributes(ServiceURL URL, Vector scopes, Vector attributeIds)
	throws ServiceLocationException {

	Assert.nonNullParameter(URL, "URL");
	Assert.nonNullParameter(scopes, "scopes");
	Assert.nonNullParameter(attributeIds, "attributeIds");

	Vector msgs = createMessages(SrvLocHeader.AttrRqst,
				     URL,
				     attributeIds,
				     URL.getServiceType(),
				     scopes);

	// Check results.

	Vector ret = new Vector();
	int i, n = msgs.size();
	int max = config.getMaximumResults();

	// We only take the first message that came back and is OK.

	for (i = 0; i < n; i++) {
	    SrvLocMsg msg = (SrvLocMsg)msgs.elementAt(i);

	    // Check for errors.

	    checkForError(msg, msgs);

	    // Select out attribute list.

	    if (msg instanceof CAttrMsg) {
		ret = ((CAttrMsg)msg).attrList;

	    } else if (msg instanceof CSAAdvert) {

		// Need to check that URL matches.

		CSAAdvert smsg = (CSAAdvert)msg;

		if (!URL.equals(smsg.URL)) {
		    continue;

		}

		ret = smsg.attrs;

	    } else if (msg instanceof CDAAdvert) {

		// Need to check that URL matches.

		CDAAdvert smsg = (CDAAdvert)msg;

		if (!URL.equals(smsg.URL)) {
		    continue;

		}

		ret = smsg.attrs;
	    }

	    // Truncate, if return is larger than maximum.

	    if (ret.size() > max) {
		ret.setSize(max);

	    }

	    // Break out, we only need one.

	    break;

	}

	// Return.

	return new ServiceLocationEnumerator(ret);
    }

    /**
     * Return all attributes for all service URL's having this
     * service type in the locale of the Locator.
     *
     * @param type The service type.
     * @param scopes	The SLP scopes of the service type.
     * @param attributeIds A vector of strings identifying the desired
     *			  attributes. A null value means return all
     *			  the attributes.  <b>Partial id strings</b> may
     *                     begin with '*' to match all ids which end with
     *                     the given suffix, or end with '*' to match all
     *                     ids which begin with a given prefix, or begin
     *                     and end with '*' to do substring matching for
     *                     ids containing the given partial id.
     * @return ServiceLocationEnumeration of ServiceLocationAttribute
     *         objects matching the ids.
     * @exception ServiceLocationException An exception is returned if the
     *					  operation fails.
     * @exception IllegalArgumentException If any of the parameters are
     *					  null or syntactically incorrect.
     * @see ServiceLocationAttribute
     *
     */

    public synchronized ServiceLocationEnumeration
	findAttributes(ServiceType type, Vector scopes, Vector attributeIds)
	throws ServiceLocationException {

	Assert.nonNullParameter(type, "URL");
	Assert.nonNullParameter(scopes, "scopes");
	Assert.nonNullParameter(attributeIds, "attributeIds");

	// Formulate and send off messages.

	Vector msgs = createMessages(SrvLocHeader.AttrRqst,
				     type,
				     attributeIds,
				     type,
				     scopes);
	// Collate results.

	Vector ret = new Vector();
	int i, n = msgs.size();
	int max = config.getMaximumResults();
	Hashtable ht = new Hashtable();

	for (i = 0; i < n && ret.size() < max; i++) {
	    SrvLocMsg msg = (SrvLocMsg)msgs.elementAt(i);

	    // Check for errors.

	    checkForError(msg, msgs);

	    Vector attrList = null;

	    // Get the instance variable.

	    if (msg instanceof CAttrMsg) {
		attrList = ((CAttrMsg)msg).attrList;

	    } else if (msg instanceof CSAAdvert) {
		attrList = ((CSAAdvert)msg).attrs;

	    } else if (msg instanceof CDAAdvert) {
		attrList = ((CDAAdvert)msg).attrs;

	    }

	    // Merge any duplicates.

	    int j, m = attrList.size();

	    for (j = 0; j < m; j++) {
		ServiceLocationAttribute attr =
		    (ServiceLocationAttribute)attrList.elementAt(j);
	
		ServiceLocationAttribute.mergeDuplicateAttributes(attr,
								  ht,
								  ret,
								  true);

		if (ret.size() >= max) {
		    break;

		}
	    }
	}

	// Return.

	return new ServiceLocationEnumerator(ret);
    }

    // Execute the message request, returning messages.

    private Vector
	createMessages(int msgType,
		       Object t1,
		       Object t2,
		       ServiceType type,
		       Vector scopes)
	throws ServiceLocationException {

	// Validate, lower case scopes.

	DATable.validateScopes(scopes, locale);

	SrvLocMsg multiMsg = null;
	SrvLocMsg uniMsg = null;
	Vector daAddresses = null;
	Vector multiCastScopes = null;

	// Get the hashtable of unicast DA addresses and multicast scopes.

	Hashtable daRecords = dat.findDAScopes(scopes);

	// Get multicast scopes and DA addresses.

	multiCastScopes =
	    (Vector)daRecords.get(DATable.MULTICAST_KEY);

	daAddresses =
	    (Vector)daRecords.get(DATable.UNICAST_KEY);

	// Special case for service request and attribute request
	//  if the user is looking for a special SLP type.

	if (((msgType == SrvLocHeader.SrvReq) ||
	    (msgType == SrvLocHeader.AttrRqst)) &&
	    (type.equals(Defaults.DA_SERVICE_TYPE) ||
	    type.equals(Defaults.SA_SERVICE_TYPE))) {

	    multiCastScopes = scopes;
	    daAddresses = null;

	    // Get query. If an attribute request, then the user
	    //  needs to sort out the attributes.

	    String query = "";

	    if (msgType == SrvLocHeader.SrvReq) {
		query = (String)t2;

	    }

	    multiMsg = new CSrvMsg(locale, type, multiCastScopes, query);

	} else {

	    // Handle a regular message.

	    // Multicast scopes are all scopes not supported by any DA.

	    if (multiCastScopes != null) {

		switch (msgType) {

		case SrvLocHeader.SrvTypeRqst:
		    multiMsg =
			new CSrvTypeMsg(locale, (String)t1, multiCastScopes);
		    break;

		case SrvLocHeader.SrvReq:
		    multiMsg =
			new CSrvMsg(locale, type, multiCastScopes, (String)t2);
		    break;

		case SrvLocHeader.AttrRqst:

		    if (t1 instanceof ServiceURL) {
			multiMsg =
			    new CAttrMsg(locale,
					 (ServiceURL)t1,
					 multiCastScopes,
					 (Vector)t2);

		    } else {
			multiMsg =
			    new CAttrMsg(locale,
					 type,
					 multiCastScopes,
					 (Vector)t2);

		    }
		}
	    }

	    // Unicast only requires a single message because the DAs will
	    //  ignore any scopes they do not support, just as long as
	    //  they support one of them.

	    if (daAddresses != null) {
		switch (msgType) {

		case SrvLocHeader.SrvTypeRqst:
		    uniMsg =
			new CSrvTypeMsg(locale, (String)t1, scopes);
		    break;

		case SrvLocHeader.SrvReq:
		    uniMsg =
			new CSrvMsg(locale, type, scopes, (String)t2);
		    break;

		case SrvLocHeader.AttrRqst:

		    if (t1 instanceof ServiceURL) {
			uniMsg =
			    new CAttrMsg(locale,
					 (ServiceURL)t1,
					 scopes,
					 (Vector)t2);

		    } else {
			uniMsg =
			    new CAttrMsg(locale,
					 type,
					 scopes,
					 (Vector)t2);

		    }

		}
	    }
	}

	// Send off messages, return results.

	return Transact.transactUA(daAddresses,
				   uniMsg,
				   multiMsg,
				   config.getMulticastAddress());
    }

    // Check message for error code.

    private static void
	checkForError(SrvLocMsg msg, Vector v)
	throws ServiceLocationException {
	int err = msg.getErrorCode();

	if (err != ServiceLocationException.OK) {
	    if (v.size() == 1) {
		config.writeLog("single_exception",
				new Object[] {
		    new Integer(err)});
		throw
		    new ServiceLocationException((short)err,
						 "remote_error",
						 new Object[] {});
	    } else {
		config.writeLog("multiple_exception",
				new Object[] {
		    new Integer(err)});
	    }
	}
    }

    // Process the incoming vector, adding any unique returns.

    private static void addUnique(Vector incoming, Vector returns, int max) {

	int i, n = incoming.size();

	for (i = 0; i < n; i++) {
	    Object o = incoming.elementAt(i);

	    if (!returns.contains(o) && returns.size() < max) {
		returns.addElement(o);

	    }
	}
    }

}
