/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission.
 * Copyright (C) 2005 Red Hat, Inc.
 * All rights reserved.
 *
 * License: GPL (version 3 or any later version).
 * See LICENSE for details. 
 * END COPYRIGHT BLOCK **/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

/* schemaparse.c - routines to support objectclass definitions */

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include "slap.h"


/* global_oc and global_schema_csn are both protected by oc locks */
struct objclass		*global_oc;
CSN *global_schema_csn = NULL; /* Timestamp for last update CSN. NULL = epoch */
static Slapi_RWLock	*oc_lock = NULL;

static int      is_duplicate( char *target, char **list, int list_max );
static void     normalize_list( char **list );

/*
 * The oc_init_lock_callonce structure is used by NSPR to ensure
 * that oc_init_lock() is called at most once.
 */
static PRCallOnceType oc_init_lock_callonce = { 0, 0, 0 };


/* Create the objectclass read/write lock.  Returns PRSuccess if successful */
static PRStatus
oc_init_lock( void )
{
	if ( NULL == ( oc_lock = slapi_new_rwlock())) {
		slapi_log_error( SLAPI_LOG_FATAL, "oc_init_lock",
				"slapi_new_rwlock() for objectclass lock failed\n" );
		return PR_FAILURE;
	}

	return PR_SUCCESS;
}


void
oc_lock_read( void )
{
	if ( NULL != oc_lock ||
			PR_SUCCESS == PR_CallOnce( &oc_init_lock_callonce, oc_init_lock )) {
		slapi_rwlock_rdlock( oc_lock );
	}
}


void
oc_lock_write( void )
{
	if ( NULL != oc_lock ||
			PR_SUCCESS == PR_CallOnce( &oc_init_lock_callonce, oc_init_lock )) {
		slapi_rwlock_wrlock( oc_lock );
	}
}


void
oc_unlock( void )
{
	if ( oc_lock != NULL ) {
		slapi_rwlock_unlock( oc_lock );
	}
}


/*
 * Note: callers of g_get_global_oc_nolock() must hold a read or write lock
 */
struct objclass* g_get_global_oc_nolock()
{
	return global_oc;
}

/*
 * Note: callers of g_set_global_oc_nolock() must hold a write lock
 */
void
g_set_global_oc_nolock( struct objclass *newglobaloc )
{
  global_oc = newglobaloc;
}

/*
 * Note: callers of g_get_global_schema_csn() must hold a read lock
 */
const CSN *
g_get_global_schema_csn()
{
  return global_schema_csn;
}

/*
 * Note: callers of g_set_global_schema_csn() must hold a write lock.
 * csn is consumed.
 */
void
g_set_global_schema_csn(CSN *csn)
{
	CSN *tmp = NULL;
	if (NULL != global_schema_csn)
	{
		tmp = global_schema_csn;
	}
	global_schema_csn = csn;
	if (NULL != tmp)
	{
		csn_free(&tmp);
	}
}

/*
 * There are two kinds of objectclasses: 
 * Standard Objectclasses and User Defined Objectclasses
 * 
 * Standard Objectclasses are the objectclasses which come with the Directory Server.
 * These objectclasses are always expected to be there and shouldn't be accidentally
 * changed by the end user. We dont' allow these objectclasses to be deleted, and the 
 * admin CGIs will not allow the end user to change their definitions. However, we 
 * will allow these objectclasses to be redefined via ldap_modify, by doing an LDAP_MOD_ADD.
 * The new definition will override the previous definition. The updated objectclass
 * will be written out the 00user.ldif and the original definition will stay
 * whereever it was originally defined. At startup, slapd will use the last definition
 * read as the real definition of an objectclass.
 *
 * User Defined ObjectClasses are objectclasses which were added to the Directory Server 
 * by the end user. These objectclasses are also kept in 99user.ldif. These objectclasses
 * can be deleted by the end user.
 *
 * Every objectclass contains an array of attributes called oc_orig_required,
 * which are the required attributes for that objectclass which were not inherited from
 * any other objectclass. Likewise, there's also an array called oc_orig_allowed which
 * contains the allowed attributes which were not inherited from any other objectclass.
 *
 * The arrays oc_required and oc_allowed contain all the required and allowed attributes for
 * that objectclass, including the ones inherited from its parent and also the ones in
 * oc_orig_required and oc_orig_allowed. 
 *
 * When an oc is updated, we go through the global list of objectclasses and see if
 * any ocs inherited from it. If so, we delete its oc_required and oc_allowed arrays,
 * copy the oc_orig_required and oc_orig_allowed arrays to oc_required and oc_allowed, 
 * and then merge the parent's oc_required and oc_allowed onto oc_required and oc_allowed.
 *
 *
 */


static int
is_duplicate( char *target, char **list, int list_size ) {
	  int i;
	  for ( i = 0; i < list_size; i++ ) {
		  if ( !strcasecmp( target, list[i] ) ) {
			  return 1;
		  }
	  }
	  return 0;
}

/*
 * Make normalized copies of all non-duplicate values in a list; free all old
 * values. The list is not resized.
 */
static void
normalize_list( char **list ) {
	int i, j;

	for ( i = 0, j = 0; list != NULL && list[i] != NULL; i++ ) {
		char *norm = slapi_attr_syntax_normalize( list[i] );
		char *save = list[i];
		if ( !is_duplicate( norm, list, j ) ) {
			list[j++] = norm;
		} else {
			slapi_ch_free((void **)&norm );
		}
		slapi_ch_free((void**)&save );
	}
	for ( ; j < i; j++ ) {
		list[j] = NULL;
	}
}

/*
 * normalize types contained in object class definitions. do this
 * after the whole config file is read so there is no order dependency
 * on inclusion of attributes and object classes.
 */

void
normalize_oc( void )
{
	struct objclass	*oc;

	oc_lock_write();

	for ( oc = g_get_global_oc_nolock(); oc != NULL; oc = oc->oc_next ) {
	  LDAPDebug (LDAP_DEBUG_PARSE, 
				 "normalize_oc: normalizing '%s'\n", oc->oc_name, 0, 0);
	  /* required attributes */
	  normalize_list( oc->oc_required );
	  normalize_list( oc->oc_orig_required );
	  
	  /* optional attributes */
	  normalize_list( oc->oc_allowed );
	  normalize_list( oc->oc_orig_allowed );
	}

	oc_unlock();
}

void
normalize_oc_nolock( void )
{
	struct objclass	*oc;

	for ( oc = g_get_global_oc_nolock(); oc != NULL; oc = oc->oc_next ) {
	  LDAPDebug (LDAP_DEBUG_PARSE, 
				 "normalize_oc: normalizing '%s'\n", oc->oc_name, 0, 0);
	  /* required attributes */
	  normalize_list( oc->oc_required );
	  normalize_list( oc->oc_orig_required );
	  
	  /* optional attributes */
	  normalize_list( oc->oc_allowed );
	  normalize_list( oc->oc_orig_allowed );
	}
}

/*
 * oc_update_inheritance_nolock: 
 * If an objectclass is redefined, we need to make sure that any objectclasses
 * which inherit from the redefined objectclass have their required and allowed
 * attributes updated.
 * 
 * Every objectclass contains an array of attributes called oc_orig_required,
 * which are the required attributes for that objectclass which were not inherited from
 * any other objectclass. Likewise, there's also an array called oc_orig_allowed which
 * contains the allowed attributes which were not inherited from any other objectclass.
 *
 * The arrays oc_required and oc_allowed contain all the required and allowed attributes for
 * that objectclass, including the ones inherited from its parent and also the ones in
 * oc_orig_required and oc_orig_allowed. 
 *
 * When an oc is updated, we go through the global list of objectclasses and see if
 * any ocs inherited from it. If so, we delete its oc_requried and oc_allowed arrays,
 * copy the oc_orig_required and oc_orig_allowed arrays to oc_required and oc_allowed, 
 * and then merge the parent's oc_required and oc_allowed onto oc_required and oc_allowed.
 */

void
oc_update_inheritance_nolock( struct objclass *psuperior_oc )
{
  struct objclass *oc;
  
  for ( oc = g_get_global_oc_nolock(); oc != NULL; oc = oc->oc_next ) {
	if ( oc->oc_superior && 
		 (strcasecmp( oc->oc_superior, psuperior_oc->oc_name ) == 0) ) {
	  if (oc->oc_required ) {
		charray_free (oc->oc_required);
	  }
	  if (oc->oc_allowed) {
		charray_free (oc->oc_allowed);
	  }
	  oc->oc_required = charray_dup ( oc->oc_orig_required );
	  oc->oc_allowed = charray_dup ( oc->oc_orig_allowed );
	  charray_merge ( &(oc->oc_required), psuperior_oc->oc_required, 1 );
	  charray_merge ( &(oc->oc_allowed), psuperior_oc->oc_allowed, 1 );
	  oc_update_inheritance_nolock ( oc );
	}
  }
}
