/******************************************************************************
* Product: Adempiere ERP & CRM Smart Business Solution *
* Copyright (C) 1999-2006 ComPiere, Inc. All Rights Reserved. *
* This program is free software; you can redistribute it and/or modify it *
* under the terms version 2 of the GNU General Public License as published *
* by the Free Software Foundation. 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 General Public License for more details. *
* You should have received a copy of the GNU General Public License along *
* with this program; if not, write to the Free Software Foundation, Inc., *
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. *
* For the text or an alternative of this public license, you may reach us *
* ComPiere, Inc., 2620 Augustine Dr. #245, Santa Clara, CA 95054, USA *
* or via info@compiere.org or http://www.compiere.org/license.html *
* Contributor(s): Carlos Ruiz - globalqss *
*****************************************************************************/
package org.compiere.model;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import org.adempiere.base.IModelFactory;
import org.adempiere.base.IServiceReferenceHolder;
import org.adempiere.base.Service;
import org.adempiere.model.GenericPO;
import org.compiere.db.AdempiereDatabase;
import org.compiere.db.Database;
import org.compiere.db.partition.ITablePartitionService;
import org.compiere.util.CCache;
import org.compiere.util.CLogger;
import org.compiere.util.DB;
import org.compiere.util.Env;
import org.compiere.util.KeyNamePair;
import org.compiere.util.Msg;
import org.compiere.util.Util;
import org.idempiere.cache.ImmutableIntPOCache;
import org.idempiere.cache.ImmutablePOSupport;
/**
* Persistent Table Model
*
* Change log:
*
* - 2007-02-01 - teo_sarca - [ 1648850 ] MTable.getClass works incorrect for table "Fact_Acct"
*
*
* - 2007-08-30 - vpj-cd - [ 1784588 ] Use ModelPackage of EntityType to Find Model Class
*
* @author Jorg Janke
* @author Teo Sarca, teo.sarca@gmail.com
* BF [ 3017117 ] MTable.getClass returns bad class
* https://sourceforge.net/p/adempiere/bugs/2433/
* @version $Id: MTable.java,v 1.3 2006/07/30 00:58:04 jjanke Exp $
*/
public class MTable extends X_AD_Table implements ImmutablePOSupport
{
/**
*
*/
private static final long serialVersionUID = 6774131577483620665L;
public final static int MAX_OFFICIAL_ID = 999999;
/**
* Get MTable from Cache (immutable)
* @param AD_Table_ID id
* @return MTable
*/
public static MTable get (int AD_Table_ID)
{
return get(Env.getCtx(), AD_Table_ID);
}
/**
* Get MTable from Cache (immutable)
* @param ctx context
* @param AD_Table_ID id
* @return MTable
*/
public static MTable get (Properties ctx, int AD_Table_ID)
{
return get(ctx, AD_Table_ID, null);
} // get
/**
* Get MTable from Cache (immutable)
* @param ctx context
* @param AD_Table_ID id
* @param trxName transaction
* @return MTable
*/
public static synchronized MTable get (Properties ctx, int AD_Table_ID, String trxName)
{
Integer key = Integer.valueOf(AD_Table_ID);
MTable retValue = s_cache.get (ctx, key, e -> new MTable(ctx, e));
if (retValue != null)
return retValue;
retValue = new MTable (ctx, AD_Table_ID, trxName);
if (retValue.get_ID () == AD_Table_ID)
{
s_cache.put (key, retValue, e -> new MTable(Env.getCtx(), e));
return retValue;
}
return null;
} // get
/**
* Get updateable copy of MTable from cache
* @param ctx
* @param AD_Table_ID
* @param trxName
* @return MTable
*/
public static MTable getCopy(Properties ctx, int AD_Table_ID, String trxName)
{
MTable table = get(ctx, AD_Table_ID, trxName);
if (table != null)
table = new MTable(ctx, table, trxName);
return table;
}
/**
* Get MTable from Cache
* @param ctx context
* @param tableName case insensitive table name
* @return MTable
*/
public static synchronized MTable get (Properties ctx, String tableName)
{
return get(ctx, tableName, null);
} // get
/**
* Get MTable from Cache
* @param ctx context
* @param tableName case insensitive table name
* @param trxName
* @return MTable
*/
public static synchronized MTable get (Properties ctx, String tableName, String trxName)
{
if (tableName == null)
return null;
MTable[] tables = s_cache.values().toArray(new MTable[0]);
for (MTable retValue : tables)
{
if (tableName.equalsIgnoreCase(retValue.getTableName()))
{
return s_cache.get (ctx, retValue.get_ID(), e -> new MTable(ctx, e));
}
}
//
MTable retValue = null;
String sql = "SELECT * FROM AD_Table WHERE UPPER(TableName)=?";
PreparedStatement pstmt = null;
ResultSet rs = null;
try
{
pstmt = DB.prepareStatement (sql, trxName);
pstmt.setString(1, tableName.toUpperCase());
rs = pstmt.executeQuery ();
if (rs.next())
retValue = new MTable (ctx, rs, trxName);
}
catch (Exception e)
{
s_log.log(Level.SEVERE, sql, e);
}
finally
{
DB.close(rs, pstmt);
rs = null; pstmt = null;
}
if (retValue != null)
{
Integer key = Integer.valueOf(retValue.getAD_Table_ID());
s_cache.put (key, retValue, e -> new MTable(Env.getCtx(), e));
}
return retValue;
} // get
/**
* Get Table Name
* @param ctx context
* @param AD_Table_ID table
* @return table name
*/
public static String getTableName (Properties ctx, int AD_Table_ID)
{
return MTable.get(ctx, AD_Table_ID).getTableName();
} // getTableName
/**
* Get table accessible by current effective role (via window access).
* @param withEmptyElement if true, first element of the return array is an empty element with (-1,"")
* @param trxName optional transaction name
* @return table records (AD_Table_ID, translated Name), order by translated name
*/
public static KeyNamePair[] getWithWindowAccessKeyNamePairs(boolean withEmptyElement, String trxName)
{
final MRole role = MRole.getDefault();
boolean trl = !Env.isBaseLanguage(Env.getCtx(), "AD_Table");
String lang = Env.getAD_Language(Env.getCtx());
String sql = "SELECT DISTINCT t.AD_Table_ID,"
+ (trl ? "trl.Name" : "t.Name")
+ " FROM AD_Table t INNER JOIN AD_Tab tab ON (tab.AD_Table_ID=t.AD_Table_ID)"
+ " INNER JOIN AD_Window_Access wa ON (tab.AD_Window_ID=wa.AD_Window_ID) "
+ (trl ? "LEFT JOIN AD_Table_Trl trl on (trl.AD_Table_ID=t.AD_Table_ID and trl.AD_Language=" + DB.TO_STRING(lang) + ")" : "")
+ " WHERE "+role.getIncludedRolesWhereClause("wa.AD_Role_ID", null)
+ " AND t.IsActive='Y' AND tab.IsActive='Y' "
+ "ORDER BY 2";
return DB.getKeyNamePairsEx(trxName, sql, withEmptyElement);
}
/** Cache */
private static ImmutableIntPOCache s_cache = new ImmutableIntPOCache(Table_Name, Table_Name, 20, 0, false, 0);
/** Static Logger */
private static CLogger s_log = CLogger.getCLogger (MTable.class);
private static final CCache> s_modelFactoryCache = new CCache<>(null, "IModelFactory", 100, 120, false, 2000);
/**
* Get Java Model Class for Table
* @param tableName table name
* @return Java model class or null
*/
public static Class> getClass (String tableName)
{
IServiceReferenceHolder cache = s_modelFactoryCache.get(tableName);
if (cache != null)
{
IModelFactory service = cache.getService();
if (service != null)
{
Class> clazz = service.getClass(tableName);
if (clazz != null)
return clazz;
}
s_modelFactoryCache.remove(tableName);
}
List> factoryList = Service.locator().list(IModelFactory.class).getServiceReferences();
if (factoryList == null)
return null;
for(IServiceReferenceHolder factory : factoryList) {
IModelFactory service = factory.getService();
if (service != null) {
Class> clazz = service.getClass(tableName);
if (clazz != null)
{
s_modelFactoryCache.put(tableName, factory);
return clazz;
}
}
}
return null;
} // getClass
/**
* UUID based Constructor
* @param ctx Context
* @param AD_Table_UU UUID key
* @param trxName Transaction
*/
public MTable(Properties ctx, String AD_Table_UU, String trxName) {
super(ctx, AD_Table_UU, trxName);
if (Util.isEmpty(AD_Table_UU))
setInitialDefaults();
}
/**
* Standard Constructor
* @param ctx context
* @param AD_Table_ID id
* @param trxName transaction
*/
public MTable (Properties ctx, int AD_Table_ID, String trxName)
{
super (ctx, AD_Table_ID, trxName);
if (AD_Table_ID == 0)
setInitialDefaults();
} // MTable
/**
* Set the initial defaults for a new record
*/
private void setInitialDefaults() {
setAccessLevel (ACCESSLEVEL_SystemOnly); // 4
setEntityType (ENTITYTYPE_UserMaintained); // U
setIsChangeLog (false);
setIsDeleteable (false);
setIsHighVolume (false);
setIsSecurityEnabled (false);
setIsView (false); // N
setReplicationType (REPLICATIONTYPE_Local);
}
/**
* Load Constructor
* @param ctx context
* @param rs result set
* @param trxName transaction
*/
public MTable (Properties ctx, ResultSet rs, String trxName)
{
super(ctx, rs, trxName);
} // MTable
/**
* Copy constructor
* @param copy
*/
public MTable(MTable copy)
{
this(Env.getCtx(), copy);
}
/**
* Copy constructor
* @param ctx
* @param copy
*/
public MTable(Properties ctx, MTable copy)
{
this(ctx, copy, (String) null);
}
/**
* Copy constructor
* @param ctx
* @param copy
* @param trxName
*/
public MTable(Properties ctx, MTable copy, String trxName)
{
//-1 to avoid infinite loop
this(ctx, -1, trxName);
copyPO(copy);
this.m_columns = copy.m_columns != null ? Arrays.stream(copy.m_columns).map(e -> {return new MColumn(ctx, e, trxName);}).toArray(MColumn[]::new): null;
this.m_columnNameMap = copy.m_columnNameMap != null ? new ConcurrentHashMap(copy.m_columnNameMap) : null;
this.m_columnIdMap = copy.m_columnIdMap != null ? new ConcurrentHashMap(copy.m_columnIdMap) : null;
this.m_viewComponents = copy.m_viewComponents != null ? Arrays.stream(copy.m_viewComponents).map(e -> {return new MViewComponent(ctx, e, trxName);}).toArray(MViewComponent[]::new) : null;
}
/** Columns */
private MColumn[] m_columns = null;
/** Key Columns */
private String[] m_KeyColumns = null;
/** column name to column index map **/
private ConcurrentMap m_columnNameMap;
/** ad_column_id to column index map **/
private ConcurrentMap m_columnIdMap;
/** View Components */
private MViewComponent[] m_viewComponents = null;
/**
* Get Columns
* @param requery true to re-query from DB
* @return array of column
*/
public synchronized MColumn[] getColumns (boolean requery)
{
if (m_columns != null && !requery)
return m_columns;
m_columnNameMap = new ConcurrentHashMap();
m_columnIdMap = new ConcurrentHashMap();
String sql = "SELECT * FROM AD_Column WHERE AD_Table_ID=? AND IsActive='Y' ORDER BY ColumnName";
ArrayList list = new ArrayList();
PreparedStatement pstmt = null;
ResultSet rs = null;
try
{
pstmt = DB.prepareStatement (sql, get_TrxName());
pstmt.setInt (1, getAD_Table_ID());
rs = pstmt.executeQuery ();
while (rs.next ()) {
MColumn column = new MColumn (getCtx(), rs, get_TrxName());
if (is_Immutable())
column.markImmutable();
list.add (column);
m_columnNameMap.put(column.getColumnName().toUpperCase(), list.size() - 1);
m_columnIdMap.put(column.getAD_Column_ID(), list.size() - 1);
}
}
catch (Exception e)
{
log.log(Level.SEVERE, sql, e);
}
finally
{
DB.close(rs, pstmt);
rs = null; pstmt = null;
}
//
if (list.size() > 0 && is_Immutable())
list.stream().forEach(e -> e.markImmutable());
m_columns = new MColumn[list.size ()];
list.toArray (m_columns);
return m_columns;
} // getColumns
/**
* Get Column via column name
* @param columnName (case insensitive)
* @return MColumn if found, null otherwise
*/
public MColumn getColumn (String columnName)
{
if (columnName == null || columnName.length() == 0)
return null;
int idx = getColumnIndex(columnName);
if (idx < 0)
return null;
return m_columns[idx];
} // getColumn
/**
* Get Column Index via column name
* @param ColumnName column name (case insensitive)
* @return index of column with ColumnName or -1 if not found
*/
public synchronized int getColumnIndex (String ColumnName)
{
if (m_columns == null)
getColumns(false);
Integer i = m_columnNameMap.get(ColumnName.toUpperCase());
if (i != null)
return i.intValue();
return -1;
} // getColumnIndex
/**
* Is column exists and is not virtual ?
* @param ColumnName column name (case insensitive)
* @return true if column exists and is not virtual
*/
public synchronized boolean columnExistsInDB (String ColumnName)
{
MColumn column = getColumn(ColumnName);
return column != null && ! column.isVirtualColumn();
} // columnExistsInDB
/**
* Column exists?
* @param ColumnName column name (case insensitive)
* @return true if column exists in dictionary
*/
public synchronized boolean columnExistsInDictionary (String ColumnName)
{
return getColumnIndex(ColumnName) >= 0;
} // columnExistsInDictionary
/**
* Get Column Index
* @param AD_Column_ID column id
* @return index of column with AD_Column_ID or -1 if not found
*/
public synchronized int getColumnIndex (int AD_Column_ID)
{
if (m_columns == null)
getColumns(false);
Integer i = m_columnIdMap.get(AD_Column_ID);
if (i != null)
return i.intValue();
return -1;
} // getColumnIndex
/**
* Table is with single primary key
* @return true if table has single primary key column
*/
public boolean isSingleKey()
{
String[] keys = getKeyColumns();
return keys.length == 1;
} // isSingleKey
/**
* Get Key Columns of Table
* @return array of key column name
*/
public String[] getKeyColumns()
{
if (m_KeyColumns != null)
return m_KeyColumns;
getColumns(false);
ArrayList list = new ArrayList();
//
for (int i = 0; i < m_columns.length; i++)
{
MColumn column = m_columns[i];
if (column.isKey()) {
m_KeyColumns = new String[]{column.getColumnName()};
return m_KeyColumns;
}
if (column.isParent())
list.add(column.getColumnName());
}
//check uuid key
if (list.isEmpty()) {
MColumn uuColumn = getColumn(PO.getUUIDColumnName(getTableName()));
if (uuColumn != null) {
m_KeyColumns = new String[]{uuColumn.getColumnName()};
return m_KeyColumns;
}
}
String[] retValue = new String[list.size()];
retValue = list.toArray(retValue);
m_KeyColumns = retValue;
return m_KeyColumns;
} // getKeyColumns
/**
* @return true if table has single key column and the key column name is the same as the table name plus _ID.
*/
public boolean isIDKeyTable()
{
String idColName = getTableName() + "_ID";
return (getKeyColumns() != null && getKeyColumns().length == 1 && getKeyColumns()[0].equals(idColName));
}
/**
* @return true if table has single key column and the key column name ends with _UU.
*/
public boolean isUUIDKeyTable()
{
String uuColName = PO.getUUIDColumnName(getTableName());
return (getKeyColumns() != null && getKeyColumns().length == 1 && getKeyColumns()[0].equals(uuColName));
}
/**
* @return true if table has a UUID column (column name ends with _UU)
*/
public boolean hasUUIDKey()
{
String uuColName = PO.getUUIDColumnName(getTableName());
if (m_columns == null)
getColumns(false);
return m_columnNameMap.get(uuColName.toUpperCase()) != null;
}
/**
* Get Identifier Columns of Table (IsIdentifier=Y)
* @return array of identifier column name
*/
public String[] getIdentifierColumns() {
ArrayList listkn = new ArrayList();
for (MColumn column : getColumns(false)) {
if (column.isIdentifier())
listkn.add(new KeyNamePair(column.getSeqNo(), column.getColumnName()));
}
// Order by SeqNo
Collections.sort(listkn, new Comparator(){
public int compare(KeyNamePair s1,KeyNamePair s2){
if (s1.getKey() < s2.getKey())
return -1;
else if (s1.getKey() > s2.getKey())
return 1;
else
return 0;
}});
String[] retValue = new String[listkn.size()];
for (int i = 0; i < listkn.size(); i++) {
retValue[i] = listkn.get(i).getName();
}
return retValue;
} // getIdentifierColumns
/**
* Get PO Instance for this table
* @param Record_ID record id. 0 to create new record instance, > 0 to load existing record instance.
* @param trxName
* @return PO for Record_ID or null
*/
public PO getPO (int Record_ID, String trxName)
{
String tableName = getTableName();
if (Record_ID != 0 && !isSingleKey())
{
log.log(Level.WARNING, "(id) - Multi-Key " + tableName);
return null;
}
PO po = null;
IServiceReferenceHolder cache = s_modelFactoryCache.get(tableName);
if (cache != null)
{
IModelFactory service = cache.getService();
if (service != null)
{
po = service.getPO(tableName, Record_ID, trxName);
if (po != null)
{
if (po.get_ID() != Record_ID && Record_ID > 0)
po = null;
return po;
}
}
s_modelFactoryCache.remove(tableName);
}
List> factoryList = Service.locator().list(IModelFactory.class).getServiceReferences();
if (factoryList != null)
{
for(IServiceReferenceHolder factory : factoryList)
{
IModelFactory service = factory.getService();
if (service != null)
{
po = service.getPO(tableName, Record_ID, trxName);
if (po != null)
{
if (po.get_ID() != Record_ID && Record_ID > 0)
po = null;
s_modelFactoryCache.put(tableName, factory);
break;
}
}
}
}
if (po == null && s_modelFactoryCache.get(tableName) == null)
{
po = new GenericPO(tableName, getCtx(), Record_ID, trxName);
if (po.get_ID() != Record_ID && Record_ID > 0)
po = null;
// TODO: how to add GenericPO to the s_modelFactoryCache ??
}
return po;
} // getPO
private static final ThreadLocal