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);
}
}