/*
 * 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.
 *
 */

//  ServiceStoreFactory.java: Factory for creating ServiceStore objects.
//  Author:           James Kempf
//  Created On:       Fri Apr 17 12:14:12 1998
//  Last Modified By: James Kempf
//  Last Modified On: Mon Jan  4 15:26:34 1999
//  Update Count:     34
//

package com.sun.slp;

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

/**
 * The ServiceStoreFactory provides a way to obtain a ServiceStore
 * object. The exact implementation will depend on how the
 * DA/slpd is configured. It could be an in-memory database,
 * a connection to an LDAP server, or a persistent object
 * database.
 *
 * @author James Kempf
 */

class ServiceStoreFactory extends Object {

    private static final String DEFAULT_SERVICE_STORE =
	"com.sun.slp.ServiceStoreInMemory";

    private static final String SERVICE_STORE_PROPERTY =
	"sun.net.slp.serviceStoreClass";

    // Comment characters for deserialization.

    final private static char COMMENT_CHAR1 = '#';
    final private static char COMMENT_CHAR2 = ';';

    // Character for URL list separator.

    final private static String URL_LIST_SEP = ", ";

    // Identifies scopes pseudo-attribute.

    final private static String SCOPES_ATTR_ID = "scopes";

    /**
     * Return the ServiceStore for the SLP agent.
     *
     * @return An object supporting the ServiceStore interface.
     * @exception ServiceLocationException Thrown
     *			if the ServiceStore object can't be
     *			created or if the
     *			class implementing the ServiceStore required
     *			a network connnection (for example, an LDAP
     *			server) and the connection couldn't be made.
     */

    static ServiceStore createServiceStore()
	throws ServiceLocationException {

	return createServiceStoreFromProperty(SERVICE_STORE_PROPERTY);

    }

    // Create the appropriate ServiceStore object from the property.

    private static ServiceStore
	createServiceStoreFromProperty(String property)
	throws ServiceLocationException {

	Properties props = System.getProperties();
	String storeClassName =
	    props.getProperty(property,
			      DEFAULT_SERVICE_STORE);
	Class storeClass = null;

	try {

	    storeClass = Class.forName(storeClassName);

	} catch (ClassNotFoundException ex) {

	    throw
		new ServiceLocationException(
				ServiceLocationException.INTERNAL_SYSTEM_ERROR,
				"ssf_no_class",
				new Object[] {storeClassName});
	}

	ServiceStore store = null;

	try {

	    store = (ServiceStore)storeClass.newInstance();

	} catch (InstantiationException ex) {

	    throw
		new ServiceLocationException(
				ServiceLocationException.INTERNAL_SYSTEM_ERROR,
				"ssf_inst_ex",
				new Object[] {
		    storeClassName,
			ex.getMessage()});

	} catch (IllegalAccessException ex) {

	    throw
		new ServiceLocationException(
				ServiceLocationException.INTERNAL_SYSTEM_ERROR,
				"ssf_ill_ex",
				new Object[] {
		    storeClassName,
			ex.getMessage()});

	} catch (ClassCastException ex) {

	    throw
		new ServiceLocationException(
				ServiceLocationException.INTERNAL_SYSTEM_ERROR,
				"ssf_class_cast",
				new Object[] {storeClassName});
	}

	return store;
    }

    /**
     * Deserialize a service store from the open stream.
     *
     * @param is The object input stream for the service store.
     * @return ServiceStore deserialized from the stream.
     * @exception ServiceLocationException If anything goes
     *				wrong with the deserialization.
     */

    static ServiceStore deserializeServiceStore(BufferedReader is)
	throws ServiceLocationException {

	ServiceStore ss = new ServiceStoreInMemory();

	try {

	    deserialize(is, ss);

	} catch (IOException ex) {
	    throw
		new ServiceLocationException(
				ServiceLocationException.PARSE_ERROR,
				"ssf_io_deser",
				new Object[] {ex.getMessage()});

	}

	return ss;
    }

    // Read the service store in the standard format from the input

    private static void deserialize(BufferedReader in, ServiceStore store)
	throws IOException, ServiceLocationException {

	SLPConfig conf = SLPConfig.getSLPConfig();
	int linecount = 0;
	int scopeLinenum = 0;

	// Parse input file until no bytes left.

	while (in.ready()) {
	    linecount++;
	    String line = in.readLine().trim();

	    // Skip any empty lines at this level.

	    if (line.length() <= 0) {
		continue;

	    }

	    char cc = line.charAt(0);

	    // If initial character is "#" or ";", ignore the line.
	    //  It's a comment. Also if the line is empty.

	    if (cc == COMMENT_CHAR1 ||
		cc == COMMENT_CHAR2) {
		continue;
	    }

	    // At this level, the line must be a URL registration,
	    //  with format:
	    //
	    // service-url ", " language ", " lifetime [ ", " type ]
	    //
	    //
	    //  We allow arbitrary whitespace around commas.

	    StringTokenizer tk = new StringTokenizer(line, URL_LIST_SEP);
	    String surl = null;
	    String slang = null;
	    String slifetime = null;
	    String sType = null;

	    if (tk.hasMoreTokens()) {
		surl = tk.nextToken().trim();

		if (tk.hasMoreTokens()) {
		    slang = tk.nextToken().trim();

		    if (tk.hasMoreTokens()) {
			slifetime = tk.nextToken().trim();

			if (tk.hasMoreTokens()) {
			    sType = tk.nextToken().trim();

			    if (tk.hasMoreTokens()) {
				slang = null;
					// should be nothing more on the line.

			    }
			}
		    }
		}
	    }

	    // Check for errors.

	    if (surl == null || slifetime == null || slang == null) {
		throw
		    new ServiceLocationException(
				ServiceLocationException.PARSE_ERROR,
				"ssf_not_valid_url",
				new Object[] {line});
	    }

	    // Create the service: URL.

	    Locale locale = SLPConfig.langTagToLocale(slang);
	    ServiceURL url = null;

	    try {

		int lifetime = Integer.parseInt(slifetime);

		// If lifetime is maximum, then set to LIFETIME_PERMANENT.

		if (lifetime == ServiceURL.LIFETIME_MAXIMUM) {
		    lifetime = ServiceURL.LIFETIME_PERMANENT;

		}

		url = new ServiceURL(surl, lifetime);

		if (sType != null) {
	
		    // Check if it's OK for this service URL.

		    ServiceType utype = url.getServiceType();

		    if (utype.isServiceURL()) {
			conf.writeLog("ssf_set_servc_err",
				      new Object[] {
			    surl,
				utype});

		    } else {
			ServiceType t = new ServiceType(sType);

			if (!t.isServiceURL() &&
			    !t.equals(url.getServiceType())) {
			    url.setServiceType(t);

			}

		    }
		}

	    } catch (NumberFormatException ex) {
		throw
		    new ServiceLocationException(
				ServiceLocationException.PARSE_ERROR,
				"ssf_not_valid_lifetime",
				new Object[] {
			slifetime, new Integer(linecount)});
	
	    } catch (IllegalArgumentException ex) {
		throw
		    new ServiceLocationException(
				ServiceLocationException.PARSE_ERROR,
				"ssf_syntax_err",
				new Object[] {
			ex.getMessage(), new Integer(linecount)});

	    }

	    // Get attributes. Format should be:
	    //
	    //      attr-line    = attr-assign | keyword
	    //	attr-assign  = attr-id "=" attrval-list
	    //	keyword      = attr-id
	    //	attrval-list = attrval | attrval ", " attrval-list

	    Vector attrs = new Vector();
	    Hashtable ht = new Hashtable();
	    ServiceLocationAttribute scopeAttr = null;
	    boolean firstLine = true;

	    try {
		while (in.ready()) {
		    linecount++;
		    line = in.readLine();

		    // Empty line indicates we're done with attributes.

		    if (line.length() <= 0) {
			break;
		    }

		    // Format the line for creating. Check whether it's a
		    // keyword or not.

		    if (line.indexOf("=") != -1) {
			line = "(" + line + ")";

		    }

		    // Create the attribute from the string.

		    ServiceLocationAttribute attr =
			new ServiceLocationAttribute(line, false);

		    // If this is the scope attribute, save until later.

		    if (firstLine) {
			firstLine = false;

			if (attr.getId().equalsIgnoreCase(SCOPES_ATTR_ID)) {
			    scopeAttr = attr;
			    continue; // do NOT save as a regular attribute.

			}
		    }

		    ServiceLocationAttribute.mergeDuplicateAttributes(attr,
								      ht,
								      attrs,
								      false);

		}
	    } catch (ServiceLocationException e) {
		// tack on the line count
		e.makeAddendum(" (line " + linecount + ")");
		throw e;

	    }

	    Vector scopes = null;

	    // Use scopes we've been configured with if none.

	    if (scopeAttr == null) {
		scopes = conf.getSAConfiguredScopes();

	    } else {

		scopes = (Vector)scopeAttr.getValues();

		try {
		    // Unescape scope strings.

		    SLPHeaderV2.unescapeScopeStrings(scopes);

		    // Validate, lower case scope names.

		    DATable.validateScopes(scopes, locale);
	
		} catch (ServiceLocationException e) {
		    e.makeAddendum(" (line " + scopeLinenum + ")");
		    throw e;
		}

	    }
	
	    // We've got the attributes, the service URL, scope, and
	    //  locale, so add a record. Note that any crypto is
	    //  added when the registration is actually done.

	    store.register(url, attrs, scopes, locale, null, null);

	    // Create a CSrvReg for forwarding
	    CSrvReg creg = new CSrvReg(true, locale, url, scopes,
				       attrs, null, null);

	    ServerDATable daTable = ServerDATable.getServerDATable();
	    daTable.forwardSAMessage(creg, conf.getLoopback());

	}
    }

    // Write the service store in the standard format to the output
    // stream.

    static void serialize(BufferedWriter out, ServiceStore store)
	throws IOException, ServiceLocationException {

	Enumeration recs = store.getServiceRecordsByScope(null);

	while (recs.hasMoreElements()) {
	    ServiceStore.ServiceRecord rec =
		(ServiceStore.ServiceRecord)recs.nextElement();
	    ServiceURL url = rec.getServiceURL();
	    String surl = url.toString();
	    Vector attrs = (Vector)rec.getAttrList().clone();
	    Locale locale = rec.getLocale();
	    Vector scopes = rec.getScopes();
	    StringBuffer line = new StringBuffer();
	
	    // Compose the registration line.

	    line.append(surl);
	    line.append(", ");
	    line.append(SLPConfig.localeToLangTag(locale));
	    line.append(", ");
	    line.append(Integer.toString(url.getLifetime()));

	    // Put out the service type and naming authority if the
	    //  URL is not a service URL.

	    if (!surl.startsWith(Defaults.SERVICE_PREFIX)) {
		ServiceType type = url.getServiceType();

		line.append(", ");
		line.append(type.toString());

	    }

	    // Write out line.

	    out.write(line.toString(), 0, line.length());
	    out.newLine();

	    // Zero line buffer.

	    line.setLength(0);

	    // Insert a scope attribute, if the scope isn't simply "DEFAULT".

	    if (scopes.size() > 1 &&
		!Defaults.DEFAULT_SCOPE.equals((String)scopes.elementAt(0))) {
		attrs.insertElementAt(
				new ServiceLocationAttribute(SCOPES_ATTR_ID,
							     scopes),
				0);
	    }

	    // Write out the attributes.

	    int i, n = attrs.size();

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

		line.append(
		ServiceLocationAttribute.escapeAttributeString(attr.getId(),
							       false));
		// Add the escaped values.

		if (vals != null) {

		    line.append("=");

		    int j, m = vals.size();

		    for (j = 0; j < m; j++) {
			Object v = vals.elementAt(j);

			if (j > 0) {
			    line.append(", ");

			}

			line.append(ServiceLocationAttribute.escapeValue(v));

		    }
		}

		out.write(line.toString(), 0, line.length());
		out.newLine();

		// Zero out string buffer.

		line.setLength(0);

	    }

	    // End of registration.

	    out.newLine();
	}

	out.flush();
    }

}
