TenantTableUpdater.java

/*
** Module   : TenantTableUpdater.java
** Abstract : Implementation of TenantMetaChangeListener which synchronizes the _tenant table with the
**            events created by REST APIs.
**
** Copyright (c) 2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 OM  20250210 First revision. _Tenant table is kept in sync with TenantManager private data structure.
*/

/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
**
**   0. Attribution Requirement.
**
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
**
**   1. No License To Use Trademarks.
**
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
**
**   2. No Misrepresentation of Affiliation.
**
**     You may not represent yourself as Golden Code Development Corporation or FWD.
**
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
**
**   3. No Misrepresentation of Source or Origin.
**
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/

package com.goldencode.p2j.persist.meta;

import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.persist.lock.*;
import com.goldencode.p2j.persist.orm.*;
import com.goldencode.p2j.util.*;
import java.util.*;

import static com.goldencode.p2j.util.BlockManager.*;

/**
 * Implements {@code TenantMetaChangeListener} for a specific database.
 * The events received by {@code TenantManager} are used for synchronization of {@code _Tenant} meta table.
 * The changes are instantly visible for the 4GL users.
 */
public class TenantTableUpdater
implements TenantMetaChangeListener
{
   /**
    * The logical database whose {@code _Tenant} table this object maintains. 
    */
   private final String database;
   
   /**
    * The {@code Persistence} of the above database.
    */
   private final Persistence p;
   
   /**
    * This constructor created an object suited for maintaining the {@code _Tenant} meta table of a specific
    * logical database.
    * 
    * @param   p
    *          The {@code Persistence} of the database.
    * @param   db
    *          The name of the logical database.
    */
   public TenantTableUpdater(Persistence p, Database db)
   {
      this.database = db.getDefault().getName(); // make sure the logical database name is set
      this.p = p;
   }
   
   /**
    * Get the logical name of the database this objects manages.
    * 
    * @return  the logical name of the database this objects manages.
    */
   @Override
   public String getDatabase()
   {
      return database;
   }
   
   /**
    * A new tenant was added by REST API. Will add a new record in {@code _Tenant} table, if one with that
    * name does not exist.
    * 
    * @param   tenant
    *          A {@code TenantManager.Tenant} structure which contains meta information on the new tenant.
    * @param   errorSink
    *          A sink for errors encountered. 
    */
   @Override
   public void addNewTenant(TenantManager.Tenant tenant, List<String> errorSink)
   {
      @LegacySignature(type = InternalEntry.Type.VARIABLE, name = "bh")
      handle bh = UndoableFactory.handle();
      
      doBlock(TransactionType.FULL, "newTenantLabel", new Block((Body) () ->
      {
         try
         {
            RecordBuffer.createMeta(bh, p, "_Tenant", database); // create the buffer for the _Tenant meta table
            BufferImpl tenantBuffer = (BufferImpl) bh.getResource();
            
            logical ok = tenantBuffer.bufferCreate();
            if (ok.getValue())
            {
               Dereferenceable tenantRecord = (Dereferenceable) bh.getResource();
               
               tenantRecord.dereference("_TenantId", new integer(tenant.id()));
               tenantRecord.dereference("_Tenant-Name", new character(tenant.getTenantName()));
               tenantRecord.dereference("_Tenant-ExtId", new character(tenant.getId().toString())); /*UUID*/
               tenantRecord.dereference("_Tenant-Description", new character(tenant.getTenantDescription()));
               tenantRecord.dereference("_Tenant-Type", new integer(tenant.getType()));
               tenantRecord.dereference(1, "_Tenant-Attributes", logical.of(tenant.getStatus()));
               tenantRecord.dereference("_Tenant-Sequence-Block", new recid(0x101)); // TODO: what value?
               tenantRecord.dereference("_Tenant-DataArea-default", new integer(0x102)); // TODO: what value?
               tenantRecord.dereference("_Tenant-IndexArea-default", new integer(0x103)); // TODO: what value?
               tenantRecord.dereference("_Tenant-LobArea-default", new integer(0x104)); // TODO: what value?
               tenantRecord.dereference("_Tenant-Allocation-default", new character("Immediate"));
               
               tenantBuffer.bufferRelease();
            }
         }
         catch (Throwable t)
         {
            errorSink.add(t.getMessage());
            undoLeave(); // ignore it? success[0] remains false
         }
      }));
      
      HandleOps.delete(bh);
   }
   
   /**
    * An existing tenant was deleted.
    *  
    * @param   tenant
    *          A {@code TenantManager.Tenant} structure which contains meta information on the old tenant.
    * @param   errorSink
    *          A sink for errors encountered. 
    */
   @Override
   public void deleteTenant(TenantManager.Tenant tenant, List<String> errorSink)
   {
      @LegacySignature(type = InternalEntry.Type.VARIABLE, name = "bh")
      handle bh = UndoableFactory.handle();
      
      doBlock(TransactionType.FULL, "deleteTenantLabel", new Block((Body) () ->
      {
         try
         {
            // create the buffer for the _Tenant meta table
            RecordBuffer.createMeta(bh, p, "_Tenant", database);
            BufferImpl tenantBuffer = (BufferImpl) bh.getResource();
            
            logical ok = tenantBuffer.findFirst(
                      new character("WHERE " + database + "._Tenant._TenantId EQ " + tenant.id()),
                      LockType.EXCLUSIVE);
            // TODO: implement locating the record using its rowid, using: tenantBuffer.findByRowID(?)
            //       that would be faster on 4GL, but I am not sure it bring some performance improvement in
            //       FWD since the rowid is also a regular field in SQL like the name
            if (ok.booleanValue())
            {
               tenantBuffer.deleteRecord();
            }
            else
            {
               errorSink.add("Tenant '" + tenant.getTenantName() + "' not found.");
            }
         }
         catch (Throwable t)
         {
            errorSink.add(t.getMessage());
            undoLeave(); // ignore it, success[0] remains false
         }
      }));
      
      HandleOps.delete(bh);
   }
   
   /**
    * Change some details of a {@code _Tenant}. The record to be updated is located using the tenant's old
    * name. The information for update (including an eventual new name) is stored in {@code tenant} parameter.
    *  
    * @param   oldName
    *          The old name of the tenant (used to identify the tenant if its name has changed).
    * @param   tenant
    *          The {@code Tenant} structure which contains the updated information.
    * @param   errorSink
    *          A sink for errors encountered. 
    */
   @Override
   public void tenantChanged(String oldName, TenantManager.Tenant tenant, List<String> errorSink)
   {
      @LegacySignature(type = InternalEntry.Type.VARIABLE, name = "bh")
      handle bh = UndoableFactory.handle();
      
      doBlock(TransactionType.FULL, "tenantChangedLabel", new Block((Body) () ->
      {
         try
         {
            // create the buffer for the _Tenant meta table
            RecordBuffer.createMeta(bh, p, "_Tenant", database);
            BufferImpl tenantBuffer = (BufferImpl) bh.getResource();
            
            logical ok = tenantBuffer.findFirst(
                     new character("WHERE " + database + "._Tenant._TenantId EQ " + tenant.id()),
                     LockType.EXCLUSIVE);
            // TODO: implement locating the record using its rowid, using: tenantBuffer.findByRowID(?)
            //       that would be faster on 4GL, but I am not sure it bring some performance improvement in
            //       FWD since the rowid is also a regular field in SQL like the name
            if (ok.getValue())
            {
               Dereferenceable tenantRecord = (Dereferenceable) bh.getResource();
               
               tenantRecord.dereference("_TenantId", new integer(tenant.id()));
               tenantRecord.dereference("_Tenant-Name", new character(tenant.getTenantName()));
               tenantRecord.dereference("_Tenant-ExtId", new character(tenant.getId().toString())); /*UUID*/
               tenantRecord.dereference("_Tenant-Description", new character(tenant.getTenantDescription()));
               tenantRecord.dereference("_Tenant-Type", new integer(tenant.getType()));
               tenantRecord.dereference(1, "_Tenant-Attributes", logical.of(tenant.getStatus()));
               tenantRecord.dereference("_Tenant-Sequence-Block", new recid(0x101)); // TODO: what value?
               tenantRecord.dereference("_Tenant-DataArea-default", new integer(0x102)); // TODO: what value?
               tenantRecord.dereference("_Tenant-IndexArea-default", new integer(0x103)); // TODO: what value?
               tenantRecord.dereference("_Tenant-LobArea-default", new integer(0x104)); // TODO: what value?
               tenantRecord.dereference("_Tenant-Allocation-default", new character("Immediate"));
               
               tenantBuffer.bufferRelease();
            }
            else
            {
               errorSink.add("Tenant '" + tenant.getTenantName() + "' not found.");
            }
         }
         catch (Throwable t)
         {
            errorSink.add(t.getMessage());
            undoLeave(); // ignore it, success[0] remains false
         }
      }));
      
      HandleOps.delete(bh);
   }
   
   /**
    * Changes the status flag of a tenant. The new value of the flag is contained in {@code tenant} parameter.
    * 
    * @param   tenant
    *          The {@code Tenant} structure which contains the updated information.
    * @param   errorSink
    *          A sink for errors encountered. 
    */
   @Override
   public void tenantStateChanged(TenantManager.Tenant tenant, List<String> errorSink)
   {
      @LegacySignature(type = InternalEntry.Type.VARIABLE, name = "bh")
      handle bh = UndoableFactory.handle();
      
      doBlock(TransactionType.FULL, "tenantStateChangedLabel", new Block((Body) () ->
      {
         try
         {
            RecordBuffer.createMeta(bh, p, "_Tenant", database); // create the buffer for the _Tenant meta table
            BufferImpl tenantBuffer = (BufferImpl) bh.getResource();
            logical ok = tenantBuffer.findFirst(
                        new character("WHERE " + database + "._Tenant._TenantId EQ " + tenant.id()),
                        LockType.EXCLUSIVE);
            // TODO: implement locating the record using its rowid, using: tenantBuffer.findByRowID(?)
            //       that would be faster on 4GL, but I am not sure it bring some performance improvement in
            //       FWD since the rowid is also a regular field in SQL like the name
            if (ok.booleanValue())
            {
               Dereferenceable tenantRecord = (Dereferenceable) bh.getResource();
               tenantRecord.dereference(new int64(1), "_Tenant-Attributes", logical.of(tenant.getStatus()));
               
               tenantBuffer.bufferRelease();
            }
            else
            {
               errorSink.add("Tenant '" + tenant.getTenantName() + "' not found.");
            }
         }
         catch (Throwable t)
         {
            errorSink.add(t.getMessage());
            undoLeave(); // ignore it, success[0] remains false
         }
      }));
      
      HandleOps.delete(bh);
   }
}