/* ======================================================================
   Parts Copyright 2006 University of Leeds, Oxford University, University of the Highlands and Islands.

   Licensed under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

====================================================================== */

package org.bodington.server.realm;

import org.apache.log4j.Logger;

import java.io.*;
import java.util.*;
import java.sql.*;
import java.math.BigInteger;
import java.security.cert.*;

import org.bodington.database.*;
import org.bodington.server.*;
import org.bodington.server.resources.*;
import org.bodington.servlet.EscapedHtmlWriter;
import org.bodington.util.PasswordGenerator;
import org.bodington.util.RandomPasswordGenerator;
import org.bodington.util.ISSPasswordGenerator;
import org.bodington.util.DobPasswordGenerator;
import org.bodington.util.PasswordGeneratorException;

public class UserManagementSessionImpl 
	extends org.bodington.server.SingleResourceSession
	implements UserManagementSession
	{
    
    private static Logger log = Logger.getLogger(UserManagementSessionImpl.class);
	
	private PrimaryKey alias_id=null;
	private String type_name=null;
	private PasswordGenerator password_generator;
	private PasswordGenerator iss_generator;
	private PasswordGenerator dob_generator;
	
	public UserManagementSessionImpl()
		{
		super();
		password_generator = new RandomPasswordGenerator();
		iss_generator = new ISSPasswordGenerator();
		dob_generator = new DobPasswordGenerator();
		}
	
	private Alias getAlias()
		throws BuildingServerException
		{
		return Alias.findAlias( alias_id );
		}
	
	public synchronized void setPasswordGeneratorClass( String pgen_class )
		throws BuildingServerException
		{
		try
			{
			Class pg_class = Class.forName( pgen_class );
			if ( pg_class == null )
				{
				throw new BuildingServerException( "Unknown password generator." );
				}
			
			if ( !PasswordGenerator.class.isAssignableFrom( pg_class ) )
				{
				throw new BuildingServerException( "Specified module is not a type of password generator." );
				}
			
			PasswordGenerator pg = (PasswordGenerator)pg_class.newInstance();
			
			if ( pg == null )
				{
				throw new BuildingServerException( "Unable to create a Password Generator." );
				}
			password_generator = pg;
			}			
		catch ( Exception ex )
			{
			throw new BuildingServerException( "Problem setting up password generator: " + ex.toString() );
			}
		}
	

	// set the alias that will be used when creating users.
	public synchronized void setAliasName( String name )
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
			AliasEditor editor = getAliasEditor();
			Zone zone = editor.getEffectiveZone();
			alias_id = null;
			type_name = null;
			Alias selected_alias=null;
			
	    	if ( name==null || name.length()==0 )
	    		throw new BuildingServerException( 
	    			"null or empty alias name.", 
	    			"ERROR: When creating users you must select a type of unique identifier." );

	    	selected_alias = Alias.findAlias( name, zone );
	    	if ( selected_alias==null )
    			throw new BuildingServerException( 
	    			"findAlias returned null.", 
    				"ERROR: The selected unique identifier couldn't be found within the administrative zone " +
    				zone.getName() + "." );

	    	if ( !selected_alias.isPrimary() )
    			throw new BuildingServerException( 
	    			"Selected alias not primary.", 
	        		"ERROR: The selected unique identifier isn't a primary unique identifier." );
		        	
	    	alias_id = selected_alias.getAliasId();
			}
		finally
			{
			disposeMethodCall();
			}
		}
	
	public synchronized void setTypeName( String type )
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
			type_name = null;
			
	    	Alias alias = getAlias();
			if ( alias==null )
    			throw new BuildingServerException( 
	    			"setTypeName called while alias not set.", 
    				"ERROR: An attempt to select a type of new user was made before a valid unique identifier has been selected." );

        	if ( type==null || type.equalsIgnoreCase( "don't know" ) )
    			throw new BuildingServerException( 
	    			"null or indecisive new user type selected.", 
    				"ERROR: You have to select a valid category of user." );
		        	
        	if ( !alias.hasUserCategory( type ) )
    			throw new BuildingServerException( 
	    			"Invalid new user type selected.", 
	        		"ERROR: The selected unique identifier cannot be used to create users of the type selected." );
		    
	    	type_name = type;
			}
		finally
			{
			disposeMethodCall();
			}
		}
	
	private synchronized AliasEditor getAliasEditor()
		throws BuildingServerException
		{
		Resource r = getResource();
		if ( r == null || !(r instanceof AliasEditor) )
			throw new BuildingServerException( 
				"Wrong type of session for this type of resource.",
				"There was a technical problem attempting to access this tool." );
				
		return (AliasEditor)r;
		}
		
	private synchronized String chooseUserName( Zone zone, User user, Vector user_names )
		throws BuildingServerException
		{
		int i, j;
		String name;
		PassPhrase p;
		
		// try a series of user names with increasing numbers in
		for ( i=1; i<100; i++ )
			{
			// proposed user name
			name=zone.getPrefix() + i + user.getInitials().toLowerCase();
			
			// check a list of user names that are about to be created...
			for ( j=0; j<user_names.size(); j++ )
				{
				//is this one already assigned?
				if ( user_names.elementAt( j ).equals( name ) )
					break;
				}
		    // go around again if proposed user name was assigned
		    // to another new user.
			if ( j<user_names.size() )
				continue;

			p=PassPhrase.findPassPhraseByUserName( name );
			//if noone in the db has this user name it's OK
			if ( p==null )
				return name;
		    //if it is this person who already has the user name it's also OK
	        if ( p.getUserId().equals( user.getUserId() ) )
				return name;
			//otherwise go around again and try another user name
			}
		return null;
		}
		
	public synchronized String createUsers( BufferedReader reader, PrintWriter writer )
		throws BuildingServerException
	    {
		prepareMethodCall();
		try
			{
	    	AliasEditor editor = getAliasEditor();
	    	if ( editor == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !editor.checkPermission( Permission.EDIT ) )
				throw new BuildingServerException( "Edit access denied",
						"You don't have 'edit' level access to this resource." );


		    
			if ( alias_id == null )
				throw new BuildingServerException( "No alias.",
						"You have to select a unique identifier before you can create users." );
			if ( type_name == null )
				throw new BuildingServerException( "No type name.",
						"You have to select a type of new user before you can create users." );

	    	Vector group_list=new Vector();
	    	Vector entry_list=new Vector();
	    	Vector user_list=new Vector();
	    	Vector username_list=new Vector();
	    	Vector passphrase_list=new Vector();
		    
	    	Vector extras=new Vector();
		   String extra_field;
		   
			Alias alias;
			User user;
			PassPhrase otherpass;
	    	Zone zone = editor.getEffectiveZone();
	    	Group group;
	    	String passphrase, initials;
		    
	    	StringBuffer groups= new StringBuffer();
        	StringBuffer where=new StringBuffer();
        	where.append( "name IN ('allusers', '" );
        	where.append( zone.getPrefix() );
        	where.append( ".users'" );
        	where.append( ", 'all" );
        	where.append( type_name );
        	where.append( "', '" );
        	where.append( zone.getPrefix() );
        	where.append( "." );
        	where.append( type_name );
       		where.append( "' ) " );
		        
        	Enumeration enumeration = Group.findGroups( where.toString() );
        	for ( int i=0; enumeration.hasMoreElements(); i++ )
        		{
        		group = (Group)enumeration.nextElement();
        		group_list.addElement( group );
        		if ( i>0 )
        			groups.append( ", " );
        		groups.append( group.getName() );
        		}
		    

			alias = getAlias();
			if ( alias==null )
				throw new BuildingServerException( 
					"Alias not found.",
					"The specified unique identifier doesn't exist within the adminsitrative zone." );
					
			if ( !alias.getZoneId().equals( zone.getZoneId() ) )
				throw new BuildingServerException( 
					"Alias not found.",
					"The specified unique identifier doesn't exist within the adminsitrative zone." );

			String line, header, value, user_name, fieldname, extra_header;
			AliasEntry entry, other_alias;
			StringTokenizer tok;
			int id, i, j, k, n, line_no;
	    	char c;
	    	String[] values;
	    	
	    	int	alias_column=-1, 
	    			name_column=-1, 
	    			initials_column=-1, 
	    			surname_column=-1, 
	    			username_column=-1, 
	    			password_column=-1;
	    	int column_count;

	    	
	    	Hashtable alias_table = new Hashtable();
	    	Hashtable username_table = new Hashtable();
	    	
	    	
			try
				{
				header = reader.readLine();
				tok=new StringTokenizer( header, "\t" );
				if ( tok.countTokens()<4 )
					throw new BuildingServerException( "Invalid input at line 1.  At least four columns delimited by TABS expected in the header line." );

				extra_header="";
				for ( column_count=0; tok.hasMoreTokens(); column_count++ )
					{
					fieldname=tok.nextToken();
	    			for ( i=0; i<fieldname.length(); i++ )
						{
						c=fieldname.charAt(i);
						if ( !Character.isLetterOrDigit( c ) &&  c!= '_' )
							throw new BuildingServerException( "Column headings can only contain letters, digits or '_'.  Error at line 1" );
						}
					if ( fieldname.equalsIgnoreCase( "alias" ) )
						{
						if ( alias_column >= 0 )
							throw new BuildingServerException( "There are two 'alias' column headings.  Error at line 1" );
						alias_column=column_count;
						}
					else if ( fieldname.equalsIgnoreCase( "name" ) )
						{
						if ( name_column >= 0 )
							throw new BuildingServerException( "There are two 'name' column headings.  Error at line 1" );
						name_column=column_count;
						}
					else if ( fieldname.equalsIgnoreCase( "initials" ) )
						{
						if ( initials_column >= 0 )
							throw new BuildingServerException( "There are two 'initials' column headings.  Error at line 1" );
						initials_column=column_count;
						}
					else if ( fieldname.equalsIgnoreCase( "surname" ) )
						{
						if ( surname_column >= 0 )
							throw new BuildingServerException( "There are two 'surname' column headings.  Error at line 1" );
						surname_column=column_count;
						}
					else if ( fieldname.equalsIgnoreCase( "username" ) )
						{
						if ( username_column >= 0 )
							throw new BuildingServerException( "There are two 'username' column headings.  Error at line 1" );
						username_column=column_count;
						}
					else if ( fieldname.equalsIgnoreCase( "password" ) )
						{
						if ( password_column >= 0 )
							throw new BuildingServerException( "There are two 'password' column headings.  Error at line 1" );
						password_column=column_count;
						}
					else
						{
						extra_header += "\t" + fieldname;
						}
					}
					
				if ( alias_column<0 || name_column<0 || initials_column<0 || surname_column<0 )
					throw new BuildingServerException( "One or more of the four compulsory column headings are missing.  Error at line 1" );


				values = new String[column_count];

				for ( line_no=2;(line = reader.readLine())!=null; line_no++ )
					{
					log.debug( line );
					if ( line.trim().length()==0 )
						continue;
						
					tok=new StringTokenizer( line, "\t" );
					if ( tok.countTokens()==0 )
						continue;
					if ( tok.countTokens()!=column_count )
						throw new BuildingServerException( "Invalid input at line " + line_no + ". Exactly " + column_count + " columns delimited by TABS expected." );

					extra_field = "";
					for ( n=0; n<column_count; n++ )
						{
						values[n]=tok.nextToken();
						
						if (  n==alias_column || n==name_column || n==initials_column || n==surname_column )
							{
	    					for ( i=0; i<values[n].length(); i++ )
								{
								c=values[n].charAt(i);
								if ( !Character.isLetterOrDigit( c ) &&   
						    		c != '_'  &&  
						    		c != '-'  &&  
						    		c != ' '  && 
						    		c != '\'' && 
						    		c != '.'       )
									throw new BuildingServerException( "User data fields can only contain letters digits, '-', '_' or a space.  Error at line " + line_no );
								}
							}
						else if ( n==password_column )
							{
							if ( values[n].length()<6 )
								throw new BuildingServerException( "Passwords must contain at least six characters.  Error at line " + line_no );
	    					for ( i=0; i<values[n].length(); i++ )
								{
								c=values[n].charAt(i);
								if ( c<'!' || c>'z' )
									throw new BuildingServerException( "Passwords can only contain ASCII characters from '!' to 'Z'.  Error at line " + line_no );
								}
							}
						else if ( n==username_column )
							{

                                               /*
                                                * WebLearn commented out code; A Corfield 02/12/2003.
                                                */
/*
							if ( !values[n].startsWith( zone.getPrefix() ) )
								throw new BuildingServerException( "User names must start with '" + zone.getPrefix() + "'.  Error at line " + line_no );

*/
	    					for ( i=0; i<values[n].length(); i++ )
								{
								c=values[n].charAt(i);
								if ( !( (c>='a' && c<='z') || ( c>='0' && c<='9') ) )
									throw new BuildingServerException( "User names can only contain lower case letters or digits.  Error at line " + line_no );
								}
							}
						else
							{
							extra_field += "\t" + values[n];
							}
						}
						
					initials = values[initials_column];
					values[initials_column]="";
    				for ( i=0; i<initials.length(); i++ )
						{
						c=initials.charAt(i);
						if ( Character.isLetter( c ) )
							values[initials_column] += Character.toUpperCase( c );
						}

					if ( values[initials_column].length()<2 )
						throw new BuildingServerException( "There must be at least two initials, (most people have three or more). Error at line " + line_no   );
						
					extras.addElement( extra_field );
						
					user = new User();
					user.setName( values[name_column] );
					user.setSurname( values[surname_column] );
					user.setInitials( values[initials_column] );
					user.setZone( zone );
					user_list.addElement( user );

					if ( username_column>=0 )
						{
						otherpass = PassPhrase.findPassPhraseByUserName( values[username_column] );
						if ( otherpass != null )
							throw new BuildingServerException( "The specified user name " + values[username_column] + " is already in use.  Line " + line_no );
						user_name = values[username_column];

						if ( username_table.containsKey( user_name ) )
							throw new BuildingServerException( "The specified user name " + values[username_column] + " appears on a previous line.  Line " + line_no );
						}
					else
						{
						user_name=chooseUserName( zone, user, username_list );
						if ( user_name == null )
							throw new BuildingServerException( "Unable to create a unique user name for " + values[name_column] + " at line " + line_no );
						}
					
                    username_list.addElement( user_name );
					username_table.put( user_name, user_name );
					
					if ( password_column>=0 )
						{
						passphrase = values[password_column];
						}
					else
						{
						Properties pgen_props = new Properties();
						// may have to examine password generator and pass in other data.
						// random password generator doesn't need user properties
						passphrase = password_generator.generate( pgen_props );
						}
					
					passphrase_list.addElement( passphrase );

					
					
					if ( alias_table.containsKey( values[alias_column] ) )
						throw new BuildingServerException( "The specified alias " + values[alias_column] + " appears on a previous line.  Line " + line_no );
					alias_table.put( values[alias_column], values[alias_column] );
					
					entry = new AliasEntry();
					entry.setAlias( alias );
					entry.setUserAlias( values[alias_column] );
					//don't save entries until they are all parsed in without error
					entry_list.addElement( entry );
					
					other_alias = AliasEntry.findAliasEntry(alias, values[alias_column]);
					
					if ( other_alias!=null )
						throw new BuildingServerException( 
							"Attempt to create duplicate user.",
							"User " + values[name_column] + " already exists.  Error at line " + line_no );
					}
				}
			catch ( IOException ioex )
				{
				throw new BuildingServerException( "IO Error reading input.\n" + ioex );
				}
			catch ( PasswordGeneratorException pgex )
				{
				throw new BuildingServerException( "Error trying to generate a password:\n" + pgex );
				}
			
			writer.print( "uid\tusername\tpassword\tname\tsurname\tinitials\talias" );
			writer.println( extra_header );
			for ( j=0; j< user_list.size(); j++ )
				{
				user = (User)user_list.elementAt( j );
				user.save();
				passphrase = (String)passphrase_list.elementAt( j );
				PassPhrase pass =new PassPhrase(user);
                pass.setUserName( (String)username_list.elementAt( j ) );
                pass.changePassPhrase(passphrase);
				pass.save();
				entry = (AliasEntry)entry_list.elementAt( j );
				entry.setUser( user );
				entry.save();
		
				for ( k=0; k<group_list.size(); k++ )
					{
					group = (Group)group_list.elementAt( k );
					log.debug( "Adding " + user.getName() + " to " + group.getName() );
					group.addMember( user );
					}
				
				writer.print( user.getUserId().toString() );
				writer.print( "\t" );
				writer.print( pass.getUserName() );
				writer.print( "\t" );
				if ( !password_generator.isOffLine() )
					{
					writer.print( passphrase );
					writer.print( "\t" );
					}
				writer.print( user.getName() );
				writer.print( "\t" );
				writer.print( user.getSurname() );
				writer.print( "\t" );
				writer.print( user.getInitials() );
				writer.print( "\t" );
				writer.print( entry.getUserAlias() );
				
				String ext = (String)extras.elementAt( j );
				if ( ext.length()>0 )
					{
					writer.print( "\t" );
					writer.print( ext );
					}
					
				writer.println( "" );
				writer.flush();
				}

			for ( k=0; k<group_list.size(); k++ )
				{
				group = (Group)group_list.elementAt( k );
				group.save();
				}
			
			return groups.toString();
			}
		finally
			{
			disposeMethodCall();
			}
		}

public synchronized String removeAllStudentPasswords()
		throws BuildingServerException
		{
		PassPhrase p;
		User user;
		AliasEntry alias_entry;
		Alias alias;
		StringBuffer message = new StringBuffer();
		int last_id=0;

		try
			{
			Vector list=new Vector();
			Enumeration enumeration = PassPhrase.findPassPhrases( "pass_phrase IS NOT NULL" );
                        //Enumeration enumeration = User.findUsers( "user_id IS NOT NULL" );
			while ( enumeration.hasMoreElements() )
				list.addElement( enumeration.nextElement() );
			

			for ( int i=0; i<list.size(); i++ )
				{
				p = (PassPhrase)list.elementAt( i );

                                user = User.findUser( "user_id = " + p.getUserId());

                                alias = Alias.findAlias( "zone_id = " + user.getZoneId() + " AND user_category = 'student'" );
				if ( alias == null )
					continue;
					
				alias_entry = AliasEntry.findAliasEntry(alias, user);
				if ( alias_entry == null )
					continue;
				p.setPassPhrase(null);
                                p.save();
				}
					
			}
		catch ( Exception ex )
			{
			log.error( ex.getMessage(), ex );	       
			throw new BuildingServerException( "Problem removing password: " + ex );
			}
			
		return message.toString();
		}
        
	public synchronized String resetNullStudentPasswords()
		throws BuildingServerException
		{
		PassPhrase p;
		User user;
		UserDetail detail;
		AliasEntry alias_entry;
		Alias alias;
		StringBuffer message = new StringBuffer();
		int last_id=0;
		
		try
			{
			Vector list=new Vector();
			Enumeration enumeration = PassPhrase.findPassPhrases( "pass_phrase IS NULL" );
			while ( enumeration.hasMoreElements() )
				list.addElement( enumeration.nextElement() );
			

			for ( int i=0; i<list.size(); i++ )
				{
				p = (PassPhrase)list.elementAt( i );
				user = User.findUser( p.getUserId() );
				if ( user == null )
					continue;

				alias = Alias.findAlias( "zone_id = " + user.getZoneId() + " AND user_category = 'student'" );
				if ( alias == null )
					continue;
					
				alias_entry = AliasEntry.findAliasEntry(alias, user);
				if ( alias_entry == null )
					continue;
					
				detail = UserDetail.findUserDetail( "user_id = " + user.getUserId().toString() );
				if ( detail == null )
				    continue;
				    
				if ( detail.getDob() == null )
				    continue;
					
				resetUserPassword( user.getUserId() );
				}
					
			}
		catch ( Exception ex )
			{
			log.error( ex.getMessage(), ex );	       
			throw new BuildingServerException( "Problem setting password: " + ex );
			}
			
		return message.toString();
		}
	
	public synchronized String resetAllStudentPasswords()
		throws BuildingServerException
		{
		UserDetail ud;
		PassPhrase p;
		User user;
		AliasEntry alias_entry;
		Alias alias;
		StringBuffer message = new StringBuffer();
		int last_id=0;
		
		try
			{
			Vector list=new Vector();
			Enumeration enumeration = UserDetail.findUserDetails( "dob IS NOT NULL" );
			while ( enumeration.hasMoreElements() )
				list.addElement( enumeration.nextElement() );
			

			for ( int i=0; i<list.size(); i++ )
				{
				ud = (UserDetail)list.elementAt( i );
				user = User.findUser( ud.getUserId() );
				if ( user == null )
					continue;

				p = PassPhrase.findPassPhrase( user );
				if ( p == null )
				    continue;
				
				alias = Alias.findAlias( "zone_id = " + user.getZoneId() + " AND user_category = 'student'" );
				if ( alias == null )
					continue;
					
				alias_entry = AliasEntry.findAliasEntry(alias, user);
				if ( alias_entry == null )
					continue;
					
				resetUserPassword( user.getUserId() );
				}
					
			}
		catch ( Exception ex )
			{
			log.error( ex.getMessage(), ex );	       
			throw new BuildingServerException( "Problem setting password: " + ex );
			}
			
		return message.toString();
		}
	
		
        
	public String addUserCertificate( PrimaryKey uid, File cert_file )
		throws BuildingServerException
        {
            try
            {
                InputStream in = new FileInputStream( cert_file );
                CertificateFactory cf = CertificateFactory.getInstance( "X.509" );
                X509Certificate cert = (X509Certificate)cf.generateCertificate( in );
                in.close();

                UserX509 user_x509 = new UserX509();
                user_x509.setUserId( uid );
                user_x509.setX509Certificate( cert );
                BigInteger big = cert.getSerialNumber();
                // only bottom 32 bits are saved - this is used to 
                // help narrow searches in the database when the
                // cert is presented during authentication later
                user_x509.setSerialNumber( big.intValue() );
                user_x509.setDistinguishedName( cert.getSubjectDN().getName() );
                user_x509.save();
                
                return "Certificate saved against selected user.";
            }
            catch ( Exception ex )
            {
                log.error( ex.getMessage(),  ex );	       
                throw new BuildingServerException( "Problem setting password: " + ex );
            }
        }
        
        
		
	public synchronized String resetUserName( PrimaryKey uid )
		throws BuildingServerException
		{
		try
			{
			PasswordGenerator pg;
			User user = User.findUser( uid );
			if ( user == null )
				throw new BuildingServerException( "Can't find user in database." );
			
			Zone zone = user.getZone();
			
			PassPhrase pphrase = PassPhrase.findPassPhrase( user );
			if ( pphrase == null )
			    {
			    pphrase = new PassPhrase(user);
			    }
			
			Vector pass_list = new Vector();
			String user_name=chooseUserName( zone, user, pass_list );
			
			if ( user_name == null )
				throw new BuildingServerException( "Unable to create a user name for this user." );
			
            pphrase.setUserName( user_name )			;
            pphrase.save();
            
			return "User name reset to \"" + pphrase.getUserName() + "\". ";
			}
		catch ( Exception ex )
			{
			 log.error( ex.getMessage(), ex );	       
			throw new BuildingServerException( "Problem setting user name: " + ex );
			}
		}

	public synchronized String resetUserPassword( PrimaryKey uid )
		throws BuildingServerException
		{
		try
			{
			PasswordGenerator pg;
			User user = User.findUser( uid );
			if ( user == null )
				throw new BuildingServerException( "Can't find user in database." );
			
			Properties props = new Properties();
			PassPhrase pphrase = PassPhrase.findPassPhrase( user );
			if ( pphrase == null )
				throw new BuildingServerException( "User not found." );
			
			UserDetail detail = UserDetail.findUserDetail( "user_id = " + uid.toString() );
			AliasEntry alias_entry=null;
			Alias alias = Alias.findAlias( "zone_id = " + user.getZoneId() + " AND user_category = 'student'" );
			if ( alias!=null )	
				alias_entry = AliasEntry.findAliasEntry(alias, user);
				
			if ( detail != null && alias_entry !=null && detail.getDob() != null )
				{
				if ( user.getZoneId().intValue() == 1 )
					{
					pg = iss_generator;
    				props.put( "student_id", alias_entry.getUserAlias() );
	    			props.put( "academic_year", "1996" );
					}
				else
					pg = dob_generator;

				StringBuffer s_dob = new StringBuffer();
				s_dob.append( detail.getDobDay() );
				s_dob.append( "/" );
				s_dob.append( detail.getDobMonth() );
				s_dob.append( "/" );
				s_dob.append( detail.getDobYear() );
				
    			props.put( "date_of_birth", s_dob.toString() );

				}
			else
				{
				pg = password_generator;
				}

			String newPassword = pg.generate( props );
			pphrase.changePassPhrase( newPassword );
			pphrase.save();
			
			if ( pg.isOffLine() )
				return "Password reset. " + pg.offLineAdvice();

			return "Password reset to \"" + newPassword + "\". " + pg.offLineAdvice();
			}
		catch ( Exception ex )
			{
			log.error( ex.getMessage(), ex );	       
			throw new BuildingServerException( "Problem setting password: " + ex );
			}
		}

	public synchronized void setUserPassword( PrimaryKey uid, String p )
		throws BuildingServerException
		{
		try
			{
			PassPhrase pphrase = PassPhrase.findPassPhrase( uid );
			if ( pphrase == null )
				throw new BuildingServerException( "User not found." );
			
			pphrase.changePassPhrase( p );
			pphrase.save();
			}
		catch ( Exception ex )
			{
			throw new BuildingServerException( "Problem setting password: " + ex );
			}
		}

	public synchronized Vector findPermittedAliasIds()
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
	    	AliasEditor editor = getAliasEditor();
	    	if ( editor == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !editor.checkPermission( Permission.EDIT ) )
				throw new BuildingServerException( "Edit access denied",
						"You don't have 'edit' level access to this resource." );


			Vector list = new Vector();
			Enumeration enumeration = Alias.findAliases( "zone_id = " + editor.getEffectiveZoneId() + " AND alias_type = " + Alias.TYPE_PRIMARY );
			Alias alias;

			while ( enumeration.hasMoreElements() )
				{
				alias = (Alias)enumeration.nextElement();
				list.addElement( alias.getAliasId() );
				}
				
			return list;
			}
		finally
			{
			disposeMethodCall();
			}
		}
	
	public synchronized String nameOfPermittedAlias( PrimaryKey alias_id )
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
	    	AliasEditor editor = getAliasEditor();
	    	if ( editor == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !editor.checkPermission( Permission.EDIT ) )
				throw new BuildingServerException( "Edit access denied",
						"You don't have 'edit' level access to this resource." );

			Alias alias = Alias.findAlias( alias_id );

			if ( ! alias.getZoneId().equals( editor.getEffectiveZoneId() ) )
				throw new BuildingServerException( "Alias access denied",
						"Attempt to access unique identifier type from outside administrative zone." );
			
			if ( alias.getAliasType().intValue() != Alias.TYPE_PRIMARY )
				throw new BuildingServerException( "Alias access denied",
						"Attempt to access non-primary unique identifier." );

			return alias.getAliasName();
			}
		finally
			{
			disposeMethodCall();
			}
		}
	
	public synchronized Vector categoriesOfPermittedAlias( PrimaryKey alias_id )
		throws BuildingServerException
		{
		prepareMethodCall();
		try
			{
	    	AliasEditor editor = getAliasEditor();
	    	if ( editor == null )
	    		throw new BuildingServerException( "Can't find correct resource." );
	    	if ( !editor.checkPermission( Permission.EDIT ) )
				throw new BuildingServerException( "Edit access denied",
						"You don't have 'edit' level access to this resource." );

			Alias alias = Alias.findAlias( alias_id );

			if ( ! alias.getZoneId().equals( editor.getEffectiveZoneId() ) )
				throw new BuildingServerException( "Alias access denied",
						"Attempt to access unique identifier type from outside administrative zone." );
			
			if ( alias.getAliasType().intValue() != Alias.TYPE_PRIMARY )
				throw new BuildingServerException( "Alias access denied",
						"Attempt to access non-primary unique identifier." );
			
			Vector list = new Vector();
			Enumeration enumeration = alias.getUserCategories();
			while ( enumeration.hasMoreElements() )
				list.addElement( enumeration.nextElement() );
				
			return list;
			}
		finally
			{
			disposeMethodCall();
			}
		}

	
	
	public synchronized void addUsersToGroups( BufferedReader reader, PrintWriter writer )
		throws BuildingServerException
	    {
		prepareMethodCall();
		
		try
			{
	    	AliasEditor editor = getAliasEditor();
	    	if ( editor == null )
	    		throw new BuildingServerException( "Can't find correct resource." );

	    	if ( !editor.checkPermission( Permission.EDIT ) || !editor.checkPermission( Permission.CREATE ) )
				throw new BuildingServerException( "Edit access denied",
						"You don't have both 'edit' and 'create' levels of access to this resource." );

			String prefix = editor.getPrefix();
				
			if ( prefix == null )
				throw new BuildingServerException( "No prefix",
						"It isn't possible to determine group names at this location." );

		    
			if ( alias_id == null )
				throw new BuildingServerException( "No alias.",
						"You have to select a unique identifier before you can create users." );

	    	Vector user_list=new Vector();
	    	Vector member_list=new Vector();
	    	Hashtable group_list=new Hashtable();
		    
	    
			Alias alias;
			User user;
	    	Zone zone = editor.getEffectiveZone();
	    	Group group;
		    
			alias = getAlias();
			if ( alias==null )
				throw new BuildingServerException( 
					"Alias not found.",
					"The specified unique identifier doesn't exist within the adminsitrative zone." );
					
			if ( !alias.getZoneId().equals( zone.getZoneId() ) )
				throw new BuildingServerException( 
					"Alias not found.",
					"The specified unique identifier doesn't exist within the adminsitrative zone." );

			String line, value, user_name;
			AliasEntry entry, other_alias;
			StringTokenizer tok;
			int id, i, j, k, n, line_no;
	    	char c;
	    	String[] values = new String[2];
			try
				{
				line = reader.readLine();
				for ( line_no=1; line!=null; line_no++ )
					{
					log.debug( line );
					if ( line.trim().length()==0 )
						continue;
						
					tok=new StringTokenizer( line, "\t" );
					if ( tok.countTokens()==0 )
						continue;
					if ( tok.countTokens()!=2 )
						throw new BuildingServerException( "Invalid input at line " + line_no + ": exactly two columns delimited by TABS expected.",
																		"Invalid input at line " + line_no + ": exactly two columns delimited by TABS expected." );

					for ( n=0; n<2; n++ )
						{
						values[n]=tok.nextToken();
	    				for ( i=0; i<values[n].length(); i++ )
							{
							c=values[n].charAt(i);
							if ( !Character.isLetterOrDigit( c ) &&   
						    	c != '_'  &&  
						    	c != '-' )
								throw new BuildingServerException( "Values can only contain letters, digits, '-' or '_'.  Error at line " + line_no,
																				"Values can only contain letters, digits, '-' or '_'.  Error at line " + line_no );
							}
						}
					
					log.debug( "alias_id = " + alias.getAliasId().toString() + " AND user_alias = '" + values[0] + "'" );
					entry = AliasEntry.findAliasEntry( alias, values[0] );
					if ( entry == null )
						throw new BuildingServerException( "Unidentified user at line " + line_no + "." ,
																		"Unidentified user at line " + line_no + "." );
					
					user_list.addElement( entry.getUserId() );
					member_list.addElement( values[1] );
					group_list.put( values[1], values[1] );
					
					line = reader.readLine();
					}
				}
			catch ( IOException ioex )
				{
				throw new BuildingServerException( "IO Error reading input.\n" + ioex );
				}

			
			writer.println( "Input parsed but not acted on." );
			
			Enumeration enumeration = group_list.keys();
			String g_name, res_name, g_full_name;
			ResourceTree tree = ResourceTreeManager.getInstance();
			Resource g_res;
			Group g;
			boolean completed = false;
			Connection con=null;
			StringBuffer message = new StringBuffer();;
			
			while ( enumeration.hasMoreElements() )
				{
				g_name = (String)enumeration.nextElement();
				g_full_name = prefix + g_name;
				
				g = Group.findGroupByName(g_full_name);
				
				res_name = editor.getFullName() + g_name + "/";
				g_res = tree.findResource( res_name );
				
				writer.print( "Group: " + g_full_name );
				writer.print( g==null?" doesn't exist ":" exists already " );
				
				writer.print( " Editor: " + res_name );
				writer.print( g_res==null?" doesn't exist<BR>":" exists already<BR>" );
				
				if ( g==null && g_res==null )
					{
					// create new group in a new group editor tool
					try
						{
						synchronized ( tree )
							{
							g_res=new Resource();
							g_res.setName( g_name );
							g_res.setTitle( EscapedHtmlWriter.filter(g_full_name) );
							g_res.setDescription( editor.getDescription() );
							g_res.setIntroduction( "This is an automatically generated group editing tool." );
							g_res.setHttpFacilityNo( 6 );
							g_res.setUseParentAcl( true );

											
							con=BuildingContext.getContext().getConnection();
							//may need to rollback after several
							//operations
							con.setAutoCommit( false );
							tree.addResource( editor, g_res );

							g = new Group();
							g.setResourceId( g_res.getResourceId() );
							g.setName( g_full_name );
							g.setDescription( editor.getDescription() );
							g.save();

							con.commit();
							con.setAutoCommit( true );
							completed=true;
							
							}
						}
					catch ( Exception ex )
						{
					    log.error( ex.getMessage(), ex );	       
						message.append( "Problem storing resource." );
						message.append( ex.getMessage() );
						}
					finally
						{
						if ( !completed )
							{
							try
								{
								if ( tree!=null && g_res!=null )
									tree.removeResource( g_res );
								}
							catch ( Exception ex )
								{
							    log.error( ex.getMessage(), ex );	       
								message.append( " A problem occurred cleaning up after the failure to create " +
													"the resource.  A 'phantom' resource may appear in the list "  +
													"of resources: " + ex.getMessage() );
								}

							try
								{
								if ( con!=null )
									con.rollback();
								}
							catch ( SQLException sqlex )
								{
								log.error( sqlex.getMessage(), sqlex );	       
								message.append( " Unable to roll back database operations." + sqlex.getMessage() );
								}
							
							}

						if ( message.length()>0 )
							throw new BuildingServerException( message.toString() );
						}

					}
				else
					{
					// check validity of existing group
					if ( !(g!=null && g_res!=null && g_res.getResourceId().equals( g.getResourceId() ) ) )
						{
						if ( g!=null )
							throw new BuildingServerException( 
								"Group " + g_full_name + " exists but in wrong location. ",
								"Group " + g_full_name + " exists but in wrong location. " );

						throw new BuildingServerException( 
							"Resource " + res_name + " exists but doesn't contain the group. "+ g_full_name + ".",
							"Resource " + res_name + " exists but doesn't contain the group. "+ g_full_name + "." );
						}
					writer.println( "Using group: " + g_full_name );
					}
				}

			PrimaryKey uid;
			for ( i=0; i< user_list.size(); i++ )
				{
				uid = (PrimaryKey)user_list.elementAt( i );
				user = User.findUser( uid );
				
				g_name = member_list.elementAt( i ).toString();
				g_full_name = prefix + g_name;
				g = Group.findGroupByName(g_full_name);

				writer.print( "User: " + user.getName() );
				writer.print( " Group Id: " + (g==null?"none":g.getGroupId().toString()) );
				writer.print( " Group Name: " + g_full_name + "<BR>" );
				
				if ( g==null )
					{
					writer.println( " Skipped " );
					continue;
					}
					
				g.addMember( user );
				g.save();
				writer.println( " Added " );
				}

			writer.flush();
			}
		finally
			{
			disposeMethodCall();
			}
		}



	}
	
