// 
// The Waveset SPML General License 
// 
// Version 0.1, April 2003
// Copyright (C) 2003 Waveset Technologies, Inc..
// 6034 West Courtyard Drive, Suite 210, Austin, Texas 78730
// All rights reserved.
// 
// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
// 
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions, and the disclaimers in Sections 6
//    and 7 below.
// 
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the disclaimers in Sections 6
//    and 7 below in the documentation and/or other materials provided
//    with the distribution.
// 
// 3. The end-user documentation included with the redistribution, if
//    any, must include the following acknowledgment:
// 
//    "This product includes software developed by 
//     Waveset Technologies, Inc. (www.waveset.com)."
// 
//    Alternately, this acknowledgment may appear in the software itself, if
//    and wherever such third-party acknowledgments normally appear.
//  
// 4. The names "Waveset" and "Waveset Technologies, Inc." must not be
//    used to endorse or promote products derived from this software
//    without the prior written permission of Waveset. For written
//    permission, please contact www.waveset.com.
//  
// 5. Products derived from this software may not be called "Waveset",
//    nor may "Waveset" appear in their name, without the prior written
//    permission of Waveset.
//  
// 6. NO WARRANTY 
// 
//    THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED
//    OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
//    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
//    DISCLAIMED.
// 
// 7.  LIMITATION OF LIABILITY
// 
//    IN NO EVENT SHALL THE WAVESET OR ITS LICENSORS BE LIABLE FOR ANY
//    DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
//    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
//    GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
//    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
//    IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
//    OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
//    ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// 
// End of Terms and Conditions
// ====================================================================
//
// Component Name: org.openspml.client.LighthouseClient
//
// Author(s): Jeff Larson
//
// Description:
//
// A wrapper around the generic SpmlClient to provide convenient interfaces
// more specific to Lighthouse.
//

package org.openspml.client;

import java.util.*;
import java.net.URL;

import org.openspml.util.*;
import org.openspml.message.*;
import org.openspml.client.*;

/**
 * A class that wraps a <code>SpmlClient</code> instance to provide
 * a more Lighthouse-specific interface.  All requests made using
 * this client are ultimately implemented with SPML requests.
 * <p>
 * Note that authentication is currently out of scope in the SPML
 * specification, but all vendors will ultimately require some form
 * of authentication.  In this implementation, it is requried that
 * the client make an SPML extended request to login to the lighthouse.
 * Credentials are passed as attributes of the extended requst.  Once
 * authenticated, a <b>session token</b> is returned as an operational
 * attribute of the login response.  This token must be included
 * as an operational attribute of all subsequent SPML requests.  Management
 * of the session token is handled automatically by LighthouseClient.
 */
public class LighthouseClient extends SpmlClient {

    //////////////////////////////////////////////////////////////////////
    //
    // Fields
    //
    //////////////////////////////////////////////////////////////////////

    /**
     * The "SOAP action" name.  If you are using the Apache SOAP router	
     * this is used to dispatch the request.  If you are using Lighthouse
     * it is not required.
     */
    static final String SOAP_ACTION = "urn:lighthouse";

    /**
     * Lighthouse account name.
     */
    String _user;

    /**
     * Lighthouse account password.  Removed as soon as
     * a session token has been established.
     */
    String _password;

    /**
     * Session token.  Returned by a login request, included
     * as an operational attribute on all other requests, 
     * refreshed after each request.
     */
    String _token;

    //////////////////////////////////////////////////////////////////////
    //
    // Constructors
    //
    //////////////////////////////////////////////////////////////////////

    /**
     * Construct a new lighthouse client.  Before calling
     * the request methods, the host, port, user, and password
     * fields must be specified.        
     */
    public LighthouseClient() {
    }

    /**
     * Set the name of a Lighthouse user to use when authenticating
     * to the Lighthouse server.
     */
    public void setUser(String s) {
        _user = s;
        _token = null;
    }

    /**
     * Set the password to use when authenticating to the 
     * Lighthouse server.
     */
    public void setPassword(String s) {
        _password = s;
    }

    //////////////////////////////////////////////////////////////////////
    //
    // Session Handling
    //
    //////////////////////////////////////////////////////////////////////

    /**
     * Login to the Lighthouse server. 
     */
    public void login() throws SpmlException {

	// only try to get a token if a user and password have
	// been specified, the server may be configured for auto-authentication

        if (_token == null && _user != null) {
            
            ExtendedRequest req = new ExtendedRequest();
            req.setOperationIdentifier("login");
            req.setAttribute("accountId", _user);
            req.setAttribute("password", _password);

            // note that since we're called implicitly by our
            // request() overload, that we have to explicitly call
            // the superclass method
            
            SpmlResponse res = super.request(req);
            if (res == null)
                throw new SpmlException("No response from server");

            throwErrors(res);

            // note that this is returned as an operational attribute
            // like all other requests
            refreshToken(res);
        }
    }

    /**
     * Logout of the Lighthouse server.
     */
    public void logout() throws SpmlException {

        if (_token != null) {
            
            ExtendedRequest req = new ExtendedRequest();
            req.setOperationIdentifier("logout");

            ExtendedResponse res = extendedRequest(req);
            if (res == null)
                throw new SpmlException("No response from server");

            // not expecting anything, could look for errors
            _token = null;
        }
    }

    private void addToken(SpmlRequest req) throws SpmlException {

        if (_token == null)
            login();
        
	if (_token != null)
	    req.setOperationalAttribute("session", _token);
    }

    private void refreshToken(SpmlResponse res) throws SpmlException {

        Object o = res.getOperationalAttributeValue("session");
        if (o != null)
            _token = o.toString();
        else {
            // hmm, don't just trash the error, it may already be
            // trying to tell us something?
	    // don't throw! the server may be configured for 
	    // auto-authentication
            //throw new SpmlException("No session token returned");
        }
    }

    //////////////////////////////////////////////////////////////////////
    //
    // Requests
    //
    //////////////////////////////////////////////////////////////////////

    /**
     * Overload this to ensure a login before proceeding.       
     */
    public SpmlResponse request(SpmlRequest req) throws SpmlException {

        SpmlResponse res = null;

        login();
        addToken(req);

        res = super.request(req);

        refreshToken(res);

        return res;
    }

    /**
     * Return the attriutes of a Lighthouse user as a Map.
     * <p>
     * This is implemented using an SPML <searchRequest>, expecting
     * a single <searchResponseEntry> and converting the list
     * of <attr>s into a Map.
     */
    public Map getUser(String accountId) throws SpmlException {

	Map user = null;

	SearchRequest req = new SearchRequest();
        req.addCondition("objectclass", "user");
	req.addCondition("identifier", accountId);
        req.addAttribute("view");

	SearchResponse response = searchRequest(req);
        throwErrors(response);

	List results = response.getResults();
	if (results != null && results.size() > 0) {
	    SearchResult res = (SearchResult)results.get(0);
	    user = new HashMap();
	    Identifier id = res.getIdentifier();
	    user.put("accountId", id.getId());

	    List atts = res.getAttributes();
	    if (atts != null) {
		Iterator it = atts.iterator();
		while (it.hasNext()) {
		    Attribute att = (Attribute)it.next();
		    user.put(att.getName(), att.getValue());
		}
	    }
	}
	
	return user;
    }

    /**
     * Delete the Lighthouse user and deprovision all associated resource
     * accounts.  If the "targets" list is passed, we will only deprovision
     * those accounts on the list.  If targets is non-null, the Lighthouse
     * account is deleted only if the name "Lighthouse" appears on the list.
     * <p>
     * This is implemented as an SPML <deleteRequest> passing the
     * target list as an operational attribute.
     */
    public void deleteUser(String accountId, List targets) 
        throws SpmlException {

	DeleteRequest req = new DeleteRequest();
        req.setIdentifier(accountId);
        req.setOperationalAttribute("targets", targets);

	SpmlResponse response = request(req);
        throwErrors(response);
    }


    /**
     * Create a new Lighthouse user, with attributes specified in a Map.
     * <p>
     * This is implemented as an SPML <addRequest>.
     */
    public void createUser(String accountId, Map attributes) 
        throws SpmlException {

	AddRequest req = new AddRequest();
        req.setIdentifier(accountId);
        req.setAttributes(attributes);
        req.setObjectClass("User");             // not required

	SpmlResponse response = request(req);
        throwErrors(response);
    }

    /**
     * Update the attributes of a user.  The attributes to be modified
     * are passed in a Map.  The current values are replaced.
     */
    public void updateUser(String accountId, Map modifications) 
        throws SpmlException {

	ModifyRequest req = new ModifyRequest();
        req.setIdentifier(accountId);
        req.setModifications(modifications);

	SpmlResponse response = request(req);
        throwErrors(response);
    }

}
