create or replace package OFS_private is
  -- business rules enforcement routines
  procedure change_clear;
  procedure change_verify_row(p_new_id in OFS_ITEMS.id%type, 
    p_new_item_type in OFS_ITEMS.item_type%type, 
    p_old_item_type in OFS_ITEMS.item_type%type, 
    p_old_locked_by in OFS_ITEMS.locked_by%type, 
    p_new_locked_by in OFS_ITEMS.locked_by%type, 
    p_old_read_only in OFS_ITEMS.read_only%type, 
    p_new_read_only in OFS_ITEMS.read_only%type, 
    p_op in char);
  procedure change_register(p_id in OFS_ITEMS.id%type, 
    p_parent_id in OFS_ITEMS.id%type, p_op in char);
  procedure change_verify;
  function os_user return varchar2;
  procedure set_os_user(p_user in varchar2);
  procedure force_unlock(p_id in OFS_ITEMS.id%type);
  
  -- test helper routines
  procedure test_count_recs(p_num in integer, 
    p_ok_msg in varchar2, p_err_msg in varchar2);
  procedure test_count_cursor(p_crs in ofs.dir_cursor,
    p_num in integer, p_ok_msg in varchar2, 
    p_err_msg in varchar2);
  procedure test_error(p_error in varchar2, 
    p_ok_msg in varchar2, p_err_msg in varchar2);
  procedure test_attr(p_path in varchar2, p_attr in varchar2,
    p_val in varchar2, p_ok_msg in varchar2, p_err_msg in varchar2);
end;
/
create or replace package body OFS_private is
  type changed_ID_rec is 
    record (
      id        OFS_ITEMS.id%type,
      parent_id OFS_ITEMS.id%type,
      op        char(1)
    );
  type changed_ID_array is 
    table of changed_ID_rec index by pls_integer;

  changed_IDs    changed_ID_array;
  changed_ID_ind pls_integer;
  g_os_user      OFS_ITEMS.created_by%type;

  procedure change_error(p_msg varchar2, p_id in OFS_ITEMS.id%type, 
                         p_op in char) is
  begin
    raise_application_error(-20000, p_msg || 
      '. Op=' || p_op || ', id=' || to_char(p_id));
  end;

  procedure change_clear is
  begin
    changed_IDs.delete;
    changed_ID_ind := 1;
  end;
  
  procedure change_register(p_id in OFS_ITEMS.id%type, 
                            p_parent_id in OFS_ITEMS.id%type,
                            p_op in char) is
  begin
    changed_IDs(changed_ID_ind).id := p_id;
    changed_IDs(changed_ID_ind).parent_id := p_parent_id;
    changed_IDs(changed_ID_ind).op := p_op;
    changed_ID_ind := changed_ID_ind + 1;
  end;
    
  procedure change_verify is
    parent_item OFS_ITEMS%rowtype;
    n           integer;
  begin
    for i in 1 .. changed_ID_ind - 1 loop
      if changed_IDs(i).id <> 0 then
        begin
          select count(*)
            into n 
            from OFS_ITEMS
            where id <> parent_id
            start with id = changed_IDs(i).id
            connect by prior id <> prior parent_id and id = prior parent_id;
        exception
          when others then
            change_error('OFS-1: Circular directory references not allowed', 
              changed_IDs(i).id, changed_IDs(i).op);
        end;
        begin
          select * 
            into parent_item 
            from OFS_ITEMS
            where id = changed_IDs(i).parent_id;
          if parent_item.item_type <> 'D' then
            change_error('OFS-2: Parent OFS item must be directory', 
              changed_IDs(i).id, changed_IDs(i).op);
          end if;
          if (changed_IDs(i).op in ('I', 'D')) and
             (parent_item.read_only = 'Y') then
            change_error('OFS-3: Parent directory is read-only', 
              changed_IDs(i).id, changed_IDs(i).op);
          end if;
        exception
          when no_data_found then
            if changed_IDs(i).op <> 'D' then
              raise;
            end if;
        end;
      end if;
    end loop;
    change_clear;
  exception
    when others then
      change_clear;
      raise;
  end;

  procedure change_verify_row(p_new_id in OFS_ITEMS.id%type, 
    p_new_item_type in OFS_ITEMS.item_type%type, 
    p_old_item_type in OFS_ITEMS.item_type%type, 
    p_old_locked_by in OFS_ITEMS.locked_by%type, 
    p_new_locked_by in OFS_ITEMS.locked_by%type, 
    p_old_read_only in OFS_ITEMS.read_only%type, 
    p_new_read_only in OFS_ITEMS.read_only%type, 
    p_op in char) is
  begin    
    if p_new_item_type <> p_old_item_type then
      change_error('OFS-4: OFS item type cannot be changed', p_new_id, p_op);
    end if;
    if p_new_item_type = 'F' then
      if (p_old_locked_by is not null) and (p_old_locked_by <> os_user) then
        change_error('OFS-5: File is locked by ' || p_old_locked_by, p_new_id, p_op);
      end if;
      if not (p_old_locked_by is not null and p_new_locked_by is null) and
         (p_old_read_only = p_new_read_only) and (p_new_read_only = 'Y') then
        change_error('OFS-6: File is read-only', p_new_id, p_op);
      end if;
    elsif p_new_item_type = 'D' then
      if p_new_locked_by is not null then
        change_error('OFS-15: Cannot lock directory', p_new_id, p_op);
      end if;
      if (p_old_read_only = p_new_read_only) and (p_new_read_only = 'Y') then
        change_error('OFS-6: Directory is read-only', p_new_id, p_op);
      end if;
    end if;
  end;    

  function os_user return varchar2 is
  begin
    return g_os_user;
  end;
  
  procedure set_os_user(p_user in varchar2) is
  begin
    g_os_user := p_user;
  end;

  procedure force_unlock(p_id in OFS_ITEMS.id%type) is
    prev_usr OFS_ITEMS.locked_by%type;
    usr      OFS_ITEMS.locked_by%type;
  begin
    select locked_by
      into usr
      from ofs_items
      where id = p_id;
    if usr is not null then
      prev_usr := os_user;
      set_os_user(usr);
      begin
        update ofs_items
          set locked_by = null, date_locked = null
          where id = p_id;
        set_os_user(prev_usr);
      exception
        when others then
          set_os_user(prev_usr);
          raise;
      end;
    end if;
  end;
  
  procedure test_count_recs(p_num in integer, 
      p_ok_msg in varchar2, p_err_msg in varchar2) is
    n integer;
  begin
    select count(*)
      into n
      from ofs_items;
    if n <> p_num then
      dbms_output.put_line(substr(p_err_msg || '. Must be ' || 
        to_char(p_num) || ', is ' || to_char(n), 1, 255));
    else
      dbms_output.put_line(substr(p_ok_msg, 1, 255));
    end if;
  end;

  procedure test_count_cursor(p_crs in ofs.dir_cursor,
      p_num in integer, p_ok_msg in varchar2, p_err_msg in varchar2) is
    n integer;
    vitem_type ofs_items.item_type%type;
    vname ofs_items.name%type;
    vdate_created ofs_items.date_created%type;
    vcreated_by ofs_items.created_by%type;
    vdate_modified ofs_items.date_modified%type;
    vmodified_by ofs_items.modified_by%type;
    vread_only ofs_items.read_only%type;
    vcompressed ofs_items.compressed%type;
    vdata_length number;
    vlocked_by ofs_items.locked_by%type;
    vdate_locked ofs_items.date_locked%type;
  begin
    n := 0;
    loop
      fetch p_crs INTO vitem_type, vname, vdate_created, vcreated_by,
        vdate_modified, vmodified_by, vread_only, vcompressed,
        vdata_length, vlocked_by, vdate_locked;
      exit when p_crs%notfound;
      n := n + 1;
    end loop;
    close p_crs;
    if n <> p_num then
      dbms_output.put_line(substr(p_err_msg || '. Must be ' || 
        to_char(p_num) || ', is ' || to_char(n), 1, 255));
    else
      dbms_output.put_line(substr(p_ok_msg, 1, 255));
    end if;
  end;

  procedure test_error(p_error in varchar2, 
      p_ok_msg in varchar2, p_err_msg in varchar2) is
  begin
    if instr(sqlerrm, p_error) <> 0 then
      dbms_output.put_line(substr(p_ok_msg, 1, 255));
    else
      dbms_output.put_line(substr(p_err_msg || '. ' || sqlerrm, 1, 255));
    end if;
  end;  
  
  procedure test_attr(p_path in varchar2, p_attr in varchar2,
      p_val in varchar2, p_ok_msg in varchar2, p_err_msg in varchar2) is
    c char(1);
  begin
    c := ofs.get_attr(p_path, p_attr);
    if c = p_val then
      dbms_output.put_line(substr(p_ok_msg, 1, 255));
    else
      dbms_output.put_line(substr(p_err_msg || '. Must be [' || 
        p_val || '], is [' || c || ']', 1, 255));
    end if;
  end;

begin
  g_os_user := sys_context('USERENV', 'OS_USER');
end;
/
