/*
 * ident	"@(#)SunServerDATable.java	1.2	99/05/10 SMI"
 *
 * Copyright (c) 1999 by Sun Microsystems, Inc.
 * All rights reserved.
 *
 */

//  SCCS Status:      @(#)SunServerDATable.java	1.2	05/10/99
//  SunServerDATable.java: Server DA Table for Sun's client/SA server SLP.
//  Author:           James Kempf
//  Created On:       Wed May 20 09:58:46 1998
//  Last Modified By: James Kempf
//  Last Modified On: Mon Mar  8 14:30:29 1999
//  Update Count:     79
//

package com.sun.slp;

import java.util.*;
import java.net.*;
import java.io.*;

/**
 * SunServerDATable is a subclass class that provides the
 * implementation for DA storage on Solaris. As described in
 * the header for SunDATable, DA information is stored in the server's
 * SA table as the service type "directory-agent.sun"  with a
 * attribute, scopes. The attribute contains a list of scopes supported
 * by the DA. The service: URL of the registration contains the
 * DA address as the host, followed by the list of scopes as an attribute
 * in the URL part. An example is:
 *
 *   service:directory-agent.sun:// 199.200.200.5/scopes=eng, corp, freeb
 *
 * The scopes of the registration are the scopes provided as the Sun-specific
 * system property "sun.net.slp.SAOnlyScopes". By convention, this is
 * initialized to be the local machine name, but it may also include other
 * names.
 *
 * @version 1.2 00/10/14
 * @author James Kempf
 */

class SunServerDATable extends ServerDATable {

    // DA boot timestamp.

    static final private String TIMESTAMP_ID =
	"424242SUN-TABLE-TIMESTAMP424242";

    // Address. Makes deletion easier.

    static final private String ADDRESS_ID = "424242SUN-TABLE-ADDRESS424242";

    private ServiceTable serviceTable = null;  // SA table for regs.
    private Vector saOnlyScopes = null;	       // Scopes for SA only.

    SunServerDATable() {

	// Get the service table.

	try {

	    serviceTable = ServiceTable.getServiceTable();

	} catch (ServiceLocationException ex) {

	}

	// Get the vector of SA scopes.

	saOnlyScopes = conf.getSAOnlyScopes();

	Assert.assert(saOnlyScopes.size() > 0,
		      "no_sa_scopes",
		      new Object[0]);

    }

    /**
     * Record a new DA in the service table.
     *
     * @param URL The DAAdvert URL.
     * @param scopes The scopes.
     * @param version DA version number.
     * @param spis SPIs this DA can support
     * @return The boot timestamp in the previous registration. Used
     *         to determine if registration is necessary. If an error occurs,
     *	       the returned value is negative. If the DA is new, the return
     *         value is the maximum long value. This will cause all
     *         registrations to be forwarded, because it is larger than any
     *         current time.
     */

    public synchronized long
	recordNewDA(ServiceURL url,
		    Vector scopes,
		    long timestamp,
		    int version,
		    Vector attrs,
		    String spis) {

	String addr = url.getHost();
	long formerTimestamp = -1L;

	// We record all DAs regardless of whether we support them or not,
	//  because a UA client may be using the user selectable scoping
	//  model and therefore may want to see them.

	Vector v = (Vector)scopes.clone();

	// Add the Sun attributes.

	ServiceLocationAttribute attr =
	    new ServiceLocationAttribute(SunDATable.SCOPES_ID, scopes);
	attrs.addElement(attr);

	Vector vals = new Vector();
	vals.addElement(Long.toString(timestamp));
	attr =
	    new ServiceLocationAttribute(SunServerDATable.TIMESTAMP_ID, vals);
	attrs.addElement(attr);

	vals = new Vector();
	vals.addElement(new Integer(version));
	attr = new ServiceLocationAttribute(SunDATable.VERSION_ID, vals);
	attrs.addElement(attr);

	vals = new Vector();
	vals.addElement(url.getHost());
	attr = new ServiceLocationAttribute(SunServerDATable.ADDRESS_ID, vals);
	attrs.addElement(attr);

	// Form the URL for the DA.

	ServiceURL adURL = formServiceTableDAURL(url, attrs);

	// Reach *around* the service table for registration, because
	//  we don't need a message. The service table abstraction
	//  is basically for decoding message objects, and we already
	//  have things in the internal form needed by the service store.

	ServiceStore store = serviceTable.store;

	try {

	    // First, get the boot time stamp if there.

	    Vector tags = new Vector();
	    tags.addElement(SunServerDATable.TIMESTAMP_ID);

	    Hashtable attrRec =
		store.findAttributes(adURL,
				     saOnlyScopes,
				     tags,
				     Defaults.locale);

	    Vector formerAttrs =
		(Vector)attrRec.get(ServiceStore.FA_ATTRIBUTES);

	    // If there, then get the old timestamp.

	    if (formerAttrs != null && !(formerAttrs.size() <= 0)) {
	
		// Get the timestamp into a long.

		attr = (ServiceLocationAttribute)formerAttrs.elementAt(0);
		vals = attr.getValues();
		String stamp = (String)vals.elementAt(0);

		try {

		    formerTimestamp = Long.parseLong(stamp.trim());

		} catch (NumberFormatException ex) {

		    Assert.assert(false,
				  "ssdat_number_format",
				  new Object[0]);

		}
	    }

	    // Now register the URL.

	    store.register(adURL,
			   attrs,
			   saOnlyScopes,
			   Defaults.locale,
			   null,
			   null);

	    // Keep track of this DAs supported SPIs
	    LinkedList spiList =
		AuthBlock.commaSeparatedListToLinkedList(spis);

	    // convert addr to an InetAddress for hashing
	    InetAddress inetAddr = null;
	    try {
		inetAddr = InetAddress.getByName(addr);
	    } catch (UnknownHostException e) {}

	    // If we didn't get the InetAddress, this DA will never be used
	    // anyway
	    if (addr != null) {
		daSPIsHash.put(inetAddr, spiList);
	    }

	} catch (ServiceLocationException ex) {
	    conf.writeLog("ssdat_register_error",
			  new Object[] {
		ex.getMessage(),
		    adURL,
		    saOnlyScopes});
	}

	return formerTimestamp;
    }

    /**
     * Remove a DA. The Sun-specific convention is used to deregister
     * the URL.
     *
     * @param address The host address of the DA, from its service URL.
     * @param scopes The scopes.
     * @return True if removed, false if not.
     */

    public synchronized boolean removeDA(InetAddress address, Vector scopes) {

	// Find URLs corresponding to this address.

	String query = "(" + ADDRESS_ID + "=" + address.getHostAddress() + ")";

	// Reach *around* the service table for dregistration, because
	//  we don't need a message. The service table abstraction
	//  is basically for decoding message objects, and we already
	//  have things in the internal form needed by the service store.

	ServiceStore store = serviceTable.store;

	try {

	    Hashtable das = returnMatchingDAs(query);

	    Enumeration daURLs = das.keys();

	    while (daURLs.hasMoreElements()) {
		ServiceURL adURL = (ServiceURL)daURLs.nextElement();
		store.deregister(adURL, saOnlyScopes, null);

	    }

	} catch (ServiceLocationException ex) {
	    conf.writeLog("ssdat_deregister_error",
			  new Object[] {
		ex.getMessage(),
		    address,
		    saOnlyScopes});

	    return false;
	}

	return true;

    }

    /**
     * Return a hashtable in ServiceTable.findServices() format (e.g.
     * URL's as keys, scopes as values) for DAs matching the query.
     *
     * @param query Query for DA attributes.
     */

    public synchronized Hashtable returnMatchingDAs(String query)
	throws ServiceLocationException {
	ServiceStore store = ServiceTable.getServiceTable().store;

	// Get DA records matching the query.

	Vector saOnlyScopes = conf.getSAOnlyScopes();

	Hashtable returns =
	    store.findServices(Defaults.SUN_DA_SERVICE_TYPE.toString(),
			       saOnlyScopes,
			       query,
			       Defaults.locale);

	// Return the hashtable of services v.s. scopes.

	return (Hashtable)returns.get(ServiceStore.FS_SERVICES);
    }

    /**
     * Return a hashtable of DA equivalence classes and multicast
     * scopes. Multicast scopes are stored in the special hashtable
     * key MULTICAST_KEY. Unicast DA equivalence classes are stored
     * under the key UNICAST_KEY. This implementation goes directly
     * to the service table in the SA server for the DA addresses.
     *
     * @param scopes Scope list for DAs needed.
     * @return Hashtable with DA addresses as keys and scopes to contact
     *         them with as values. Any scopes not associated with a
     *         DA come back stored under the key MULTICAST_KEY.
     *         Unicast DA equivalence classes are stored
     * 	     under the key UNICAST_KEY.
     */

    public synchronized Hashtable findDAScopes(Vector scopes)
	throws ServiceLocationException {

	// Formulate a query for the DAs.

	int i, n = scopes.size();
	StringBuffer buf = new StringBuffer();

	for (i = 0; i < n; i++) {
	    buf.append("(");
	    buf.append(SunDATable.SCOPES_ID);
	    buf.append("=");
	    buf.append((String)scopes.elementAt(i));
	    buf.append(")");
	}

	// Add logical disjunction if more than one element.

	if (i > 1) {
	    buf.insert(0, "(|");
	    buf.append(")");

	}

	// Add version number.

	if (i > 0) {
	    buf.insert(0, "(&");

	}

	buf.append("(");
	buf.append(SunDATable.VERSION_ID);
	buf.append("=");
	buf.append((new Integer(Defaults.version)).toString());
	buf.append(")");

	// Add closing paren if there were any scopes.

	if (i > 0) {
	    buf.append(")");

	}

	ServiceStore store = serviceTable.store;

	Hashtable returns =
	    store.findServices(Defaults.SUN_DA_SERVICE_TYPE.toString(),
			       saOnlyScopes,
			       buf.toString(),
			       Defaults.locale);

	Hashtable retRec = (Hashtable)returns.get(ServiceStore.FS_SERVICES);

	// Convert to a vector. Keys are the service: URLs.

	Enumeration en = retRec.keys();
	Vector ret = new Vector();
	Vector multiScopes = (Vector)scopes.clone();
	Vector attrTags = new Vector();

	attrTags.addElement(SunDATable.SCOPES_ID);

	while (en.hasMoreElements()) {
	    ServiceURL url = (ServiceURL)en.nextElement();
	    Vector urlScopes = (Vector)retRec.get(url);

	    // Get the scope attributes for this URL.

	    Hashtable attrRec =
		store.findAttributes(url,
				     urlScopes,
				     attrTags,
				     Defaults.locale);

	    Vector retAttrs = (Vector)attrRec.get(ServiceStore.FA_ATTRIBUTES);
	    String host = url.getHost();
	    Vector retScopes = null;
	    n = retAttrs.size();

	    for (i = 0; i < n; i++) {
		ServiceLocationAttribute attr =
		    (ServiceLocationAttribute)retAttrs.elementAt(i);

		// Distinguish based on type. We assume the attributes are
		// prescreened when the URL was formed to make sure they're OK

		String id = attr.getId();
		Vector vals = attr.getValues();

		if (id.equals(SunDATable.SCOPES_ID)) {
		    retScopes = vals;

		}
	    }

	    // Add to equivalence class.

	    DATable.addToEquivClass(host, retScopes, ret);

	    // Filter scopes for any that might be multicast.

	    DATable.filterScopes(multiScopes, retScopes, false);

	}

	// Format the return.

	retRec.clear();

	if (multiScopes.size() > 0) {
	    retRec.put(DATable.MULTICAST_KEY, multiScopes);

	}

	if (ret.size() > 0) {
	    retRec.put(DATable.UNICAST_KEY, ret);

	}

	return retRec;
    }

    // Form a URL for the service table, from the DA URL and attributes.
    //  Attributes and scope have been prechecked for correctness.

    private ServiceURL formServiceTableDAURL(ServiceURL url, Vector attrs) {

	// Form up the URL part.

	StringBuffer buf = new StringBuffer();

	int i, n = attrs.size();

	for (i = 0; i < n; i++) {
	    ServiceLocationAttribute attr =
		(ServiceLocationAttribute)attrs.elementAt(i);

	    // If this is a URL attribute, then externalize and
	    //  put into URL.

	    String id = attr.getId();

	    if (id.equals(SunDATable.SCOPES_ID)) {
		String rep = "";

		try {
		    rep = attr.externalize();

		} catch (ServiceLocationException ex) {
		    conf.writeLog("ssdat_inter_attr_err",
				  new Object[] {attr, ex.getMessage()});
		    continue;

		}

		// Add semi if something already there.

		if (buf.length() > 0) {
		    buf.append(";");

		}

		// Remove parens before inserting.

		buf.append(rep.substring(1, rep.length()-1));

	    }
	}

	// Create the URL.

	ServiceURL daURL =
	    new ServiceURL(Defaults.SUN_DA_SERVICE_TYPE+
			   "://"+
			   url.getHost()+
			   "/"+
			   buf.toString(),
			   url.getLifetime());
	return daURL;

    }
}
