/******************************************************************************
* 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 *
*****************************************************************************/
package org.compiere.model;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.logging.Level;
import org.adempiere.util.ContextRunnable;
import org.compiere.Adempiere;
import org.compiere.util.CCache;
import org.compiere.util.CLogMgt;
import org.compiere.util.DB;
import org.compiere.util.DisplayType;
import org.compiere.util.Env;
import org.compiere.util.KeyNamePair;
import org.compiere.util.NamePair;
import org.compiere.util.Util;
import org.compiere.util.ValueNamePair;
/**
* An intelligent MutableComboBoxModel, which determines what can be cached.
*
* Validated - SQL is final / not dynamic
* AllLoaded - All Records are loaded
*
* Get Info about Lookup
* - SQL
* - KeyColumn
* - Zoom Target
*
* @author Jorg Janke
* @version $Id: MLookup.java,v 1.4 2006/10/07 00:58:57 jjanke Exp $
*/
public final class MLookup extends Lookup implements Serializable
{
/**
*
*/
private static final long serialVersionUID = 3339750658316918418L;
/**
* MLookup Constructor
* @param info info
* @param TabNo tab no
*/
public MLookup (MLookupInfo info, int TabNo)
{
super(info.DisplayType, info.WindowNo);
m_info = info;
m_tabNo = TabNo;
if (log.isLoggable(Level.FINE)) log.fine(m_info.KeyColumn);
// Don't load Search or CreatedBy/UpdatedBy
if (m_info.DisplayType == DisplayType.Search || m_info.DisplayType == DisplayType.SearchUU
|| m_info.IsCreadedUpdatedBy)
return;
// Don't load Parents/Keys
if (m_info.IsParent || m_info.IsKey)
{
m_hasInactive = true; // creates focus listener for dynamic loading
return; // required when parent needs to be selected (e.g. price from product)
}
} // MLookup
/** Inactive Marker Start */
public static final String INACTIVE_S = "~";
/** Inactive Marker End */
public static final String INACTIVE_E = "~";
/** Indicator for Null */
private static Integer MINUS_ONE = Integer.valueOf(-1);
/** The Lookup Info Value Object */
private MLookupInfo m_info = null;
private int m_tabNo = 0;
/** Storage of data Key-NamePair */
private volatile LinkedHashMap m_lookup = new LinkedHashMap();
/** The Data Loader */
private MLoader m_loader;
//
/** All Data loaded */
private boolean m_allLoaded = false;
/** Inactive records exists */
private boolean m_hasInactive = false;
/* Refreshing */
private boolean m_refreshing = false;
/* Refresh cache(if exists) */
private boolean m_refreshCache = false;
/** Next Read for Parent */
private long m_nextRead = 0;
/** Not in short List item Marker Start IDEMPIERE 90 */
public static final String SHORTLIST_S = "*";
/** Not in short List item Marker End IDEMPIERE 90 */
public static final String SHORTLIST_E = "*";
private boolean m_hasShortListItems = false; // IDEMPIERE 90
private final static int MAX_NAMEPAIR_CACHE_SIZE = 1000;
/**
* Dispose
*/
public void dispose()
{
if (m_info != null)
if (log.isLoggable(Level.FINE)) log.fine(m_info.KeyColumn + ": dispose");
if (m_loaderFuture != null && !m_loaderFuture.isDone())
m_loaderFuture.cancel(true);
m_loader = null;
m_loaderFuture = null;
//
if (m_lookup != null)
m_lookup.clear();
m_lookup = null;
if (m_lookupDirect != null)
m_lookupDirect.clear();
m_lookupDirect = null;
//
m_info = null;
//
super.dispose();
} // dispose
/**
* Wait until asynchronous Load Complete
*/
public void loadComplete()
{
if (m_loaderFuture != null && !m_loaderFuture.isDone())
{
try
{
m_loaderFuture.get();
}
catch (Exception ie)
{
log.log(Level.SEVERE, m_info.KeyColumn + ": Interrupted", ie);
}
m_loader = null;
m_loaderFuture = null;
}
} // loadComplete
/**
* Get NamePair/KeyNamePair for key.
* If not found return null.
* @param key key value (Integer for Keys or String for Lists)
* @return NamePair/KeyNamePair
*/
public NamePair get (Object key, boolean includeDirect)
{
if (key == null || MINUS_ONE.equals(key)) // indicator for null
return null;
//auto refresh parent lookup
if (m_info.IsParent )
{
if (m_nextRead > 0 && m_nextRead < System.currentTimeMillis())
{
m_lookup.clear();
if (m_lookupDirect != null)
m_lookupDirect.clear();
}
m_nextRead = System.currentTimeMillis() + 5000; // 5 sec
}
// try cache
NamePair retValue = (NamePair)m_lookup.get(key);
if (retValue != null)
return retValue;
// Not found and waiting for loader
if (m_loaderFuture != null && !m_loaderFuture.isDone())
{
if (log.isLoggable(Level.FINER)) log.finer((m_info.KeyColumn==null ? "ID="+m_info.Column_ID : m_info.KeyColumn) + ": waiting for Loader");
loadComplete();
// try again after loading completed
retValue = (NamePair)m_lookup.get(key);
if (retValue != null)
return retValue;
}
if (includeDirect) {
// Try to get it directly from DB
boolean cacheLocal = m_info.IsValidated ;
return getDirect(key, false, cacheLocal); // do NOT cache
}
return null;
} // get
/**
* Get NamePair/KeyNamePair for key.
* @param key
* @return NamePair/KeyNamePair or null
*/
public NamePair get(Object key) {
return get(key, true);
}
/**
* Get NamePair/KeyNamePair for key.
* If not found in local lookup cache, do not try to load direct from DB.
* @param key
* @return NamePair/KeyNamePair or null
*/
public NamePair getNoDirect(Object key) {
return get(key, false);
}
/**
* Get Display Text.
* If not found, return key embedded in "<>".
* @param key key value
* @return display text
*/
public String getDisplay (Object key)
{
if (key == null)
return "";
//
if (m_info.DisplayType==DisplayType.ChosenMultipleSelectionList || m_info.DisplayType==DisplayType.ChosenMultipleSelectionSearch
|| m_info.DisplayType==DisplayType.ChosenMultipleSelectionTable)
{
StringBuilder builder = new StringBuilder();
String[] keys = key.toString().split("[,]");
for(String k : keys)
{
if (builder.length() > 0)
builder.append(", ");
Object display = get(k);
if (display == null)
{
builder.append("<").append(k).append(">");
}
else
{
builder.append(display.toString());
}
}
return builder.toString();
}
Object display = get (key);
if (display == null){
StringBuilder msgreturn = new StringBuilder("<").append(key.toString()).append(">");
return msgreturn.toString();
}
return display.toString();
} // getDisplay
/**
* @param key key
* @return true if key exists in local lookup cache or in DB
*/
public boolean containsKey (Object key)
{
//should check direct too
if (m_lookup.containsKey(key))
return true;
else {
if (m_lookup.size() > 0)
return false;
else
return ( get(key) != null );
}
} // containsKey
/**
* @param key key
* @return true if key exists in local lookup cache (do not check DB)
*/
public boolean containsKeyNoDirect (Object key)
{
//should check direct too
if (m_lookup.containsKey(key))
return true;
else {
if (m_lookup.size() > 0)
return false;
else
return ( getNoDirect(key) != null );
}
} // containsKeyNoDirect
/**
* @return a string representation of the object.
*/
@Override
public String toString()
{
StringBuilder msgreturn = new StringBuilder("MLookup[").append(m_info.KeyColumn).append(",Column_ID=").append(m_info.Column_ID)
.append(",Size=").append(m_lookup.size()).append(",Validated=").append(isValidated())
.append("-").append(getValidation())
.append("]");
return msgreturn.toString();
} // toString
/**
* Indicates whether some other object is "equal to" this one.
* @param obj the reference object with which to compare.
* @return true if this object is the same as the obj
* argument; false otherwise.
*/
@Override
public boolean equals(Object obj)
{
if (obj instanceof MLookup)
{
MLookup ll = (MLookup)obj;
if (ll.m_info.Column_ID == this.m_info.Column_ID)
return true;
}
return false;
} // equals
@Override
public int hashCode()
{
assert false : "hashCode not designed";
return 42; // any arbitrary constant will do
}
/**
* Return Size
* @return size
*/
public int size()
{
return m_lookup.size();
} // size
/**
* Is it all loaded
* @return true if all loaded
*/
public boolean isAllLoaded()
{
return m_allLoaded;
} // isAllLoaded
/**
* Is the List fully Validated
* @return true if validated (i.e validation code have been parsed)
*/
@Override
public boolean isValidated()
{
if (m_info == null)
return false;
return isValidated(m_info);
} // isValidated
/**
* Get Validation SQL
* @return Validation SQL
*/
@Override
public String getValidation()
{
return m_info.ValidationCode;
} // getValidation
/**
* Get Reference Value ID
* @return AD_Reference_Value_ID
*/
public int getAD_Reference_Value_ID()
{
return m_info.AD_Reference_Value_ID;
} // getAD_Reference_Value_ID
/**
* Has inactive elements in list
* @return true if list contains inactive values
*/
@Override
public boolean hasInactive()
{
return m_hasInactive;
} // hasInactive
/**
* @return AD_InfoWindow_ID
*/
public int getAD_InfoWindow_ID()
{
return m_info.InfoWindowId;
}
/**
* Return data as ArrayList containing Value/KeyNamePair.
* @param onlyValidated true to reload lookup data if validation code have not been parsed or needs re-parse
* @param loadParent true to load data for lookup with IsParent=true
* @return List
*/
private ArrayList getData (boolean onlyValidated, boolean loadParent)
{
if (m_loaderFuture != null && !m_loaderFuture.isDone())
{
if (log.isLoggable(Level.FINE)) log.fine((m_info.KeyColumn==null ? "ID="+m_info.Column_ID : m_info.KeyColumn)
+ ": waiting for Loader");
loadComplete();
}
// Never Loaded (correctly)
if (!m_allLoaded || m_lookup.size() == 0)
loadData (loadParent);
// already validation included
boolean validated = this.isValidated(m_info);
if (validated)
return new ArrayList(m_lookup.values());
if (!validated && onlyValidated)
{
loadData (loadParent);
if (log.isLoggable(Level.FINE)) log.fine(m_info.KeyColumn + ": Validated - #" + m_lookup.size());
}
return new ArrayList(m_lookup.values());
} // getData
/**
* Return data as Array containing Value/KeyNamePair
* @param mandatory if not mandatory, an additional empty value is inserted
* @param onlyValidated only validated
* @param onlyActive only active
* @param temporary force load for temporary display
* @param shortlist
* @return list
*/
@Override
public ArrayList getData (boolean mandatory, boolean onlyValidated, boolean onlyActive, boolean temporary, boolean shortlist) // idempiere 90
{
// create list
ArrayList list = getData (onlyValidated, true);
// Remove inactive choices
if (onlyActive && m_hasInactive)
{
// list from the back
for (int i = list.size(); i > 0; i--)
{
Object o = list.get(i-1);
if (o != null)
{
String s = o.toString();
if (s.startsWith(INACTIVE_S) && s.endsWith(INACTIVE_E))
list.remove(i-1);
}
}
}
// Remove non short list items IDEMPIERE 90
if (shortlist && m_hasShortListItems)
{
// list from the back
for (int i = list.size(); i > 0; i--)
{
Object o = list.get(i-1);
if (o != null)
{
String s = o.toString();
if (s.startsWith(SHORTLIST_S) && s.endsWith(SHORTLIST_E))
list.remove(i-1);
}
}
}
// End Remove non short list items IDEMPIERE 90
// Add Optional (empty) selection
if (!mandatory)
{
NamePair p = null;
if (m_info.KeyColumn != null && m_info.KeyColumn.endsWith("_ID"))
p = new KeyNamePair (-1, "");
else
p = new ValueNamePair ("", "");
list.add(0, p);
}
return list;
} // getData
/** Save getDirect last return value */
private HashMap m_lookupDirect = null;
private Future> m_loaderFuture;
@Override
public NamePair getDirect (Object key, boolean saveInCache, boolean cacheLocal)
{
return getDirect(key, saveInCache, cacheLocal, null);
} // getDirect
/**
* Get Data Direct from DB.
* @param key key
* @param saveInCache true save in local lookup cache
* @param cacheLocal true to save in direct lookup cache
* @return NamePair/KeyNamePir
*/
public NamePair getDirect (Object key, boolean saveInCache, boolean cacheLocal, String trxName)
{
// Nothing to query
if (key == null || m_info.QueryDirect == null || m_info.QueryDirect.length() == 0)
return null;
if (key.toString().trim().length() == 0)
return null;
//
NamePair directValue = null;
if (m_lookupDirect != null) // Lookup cache
{
directValue = (NamePair)m_lookupDirect.get(key);
if (directValue != null)
return directValue;
}
if (log.isLoggable(Level.FINER)) log.finer(m_info.KeyColumn + ": " + key
+ ", SaveInCache=" + saveInCache + ",Local=" + cacheLocal);
String cacheKey = m_info.TableName+"|"+m_info.KeyColumn+"|"+m_info.AD_Reference_Value_ID+"|"+Env.getAD_Language(Env.getCtx());
boolean isNumber = m_info.KeyColumn.endsWith("_ID");
CCache knpCache = null;
CCache vnpCache = null;
if (isNumber)
{
knpCache = getDirectKeyNamePairCache(m_info, cacheKey);
KeyNamePair knp = knpCache.get(Integer.parseInt(key.toString()));
if (knp != null)
return knp;
}
else
{
vnpCache = getDirectValueNamePairCache(m_info, cacheKey);
ValueNamePair vnp = vnpCache.get(key.toString());
if (vnp != null)
return vnp;
}
PreparedStatement pstmt = null;
ResultSet rs = null;
try
{
// SELECT Key, Value, Name FROM ...
pstmt = DB.prepareStatement(m_info.QueryDirect, trxName);
if (isNumber)
pstmt.setInt(1, Integer.parseInt(key.toString()));
else
pstmt.setString(1, key.toString());
rs = pstmt.executeQuery();
if (rs.next())
{
StringBuilder name = new StringBuilder().append(rs.getString(3));
boolean isActive = rs.getString(4).equals("Y");
if (!isActive)
{
name.insert(0, INACTIVE_S).append(INACTIVE_E);
}
if (isNumber)
{
int keyValue = rs.getInt(1);
KeyNamePair p = new KeyNamePair(keyValue, name.toString());
if (saveInCache) // save if
m_lookup.put(Integer.valueOf(keyValue), p);
directValue = p;
knpCache.put(p.getKey(), p);
}
else
{
String value;
if (m_info.KeyColumn.endsWith("_UU"))
value = rs.getString(1);
else
value = rs.getString(2);
ValueNamePair p = new ValueNamePair(value, name.toString());
if (saveInCache) // save if
m_lookup.put(value, p);
directValue = p;
vnpCache.put(p.getValue(), p);
}
if (rs.next()) {
Level level = Level.SEVERE;
if (MChangeLog.Table_Name.equals(m_info.TableName))
level = Level.INFO;
if (log.isLoggable(level))
log.log(level, m_info.KeyColumn + ": Not unique (first returned) for " + key + " SQL=" + m_info.QueryDirect);
}
}
else
{
directValue = null;
}
if (log.isLoggable(Level.FINEST)) log.finest(m_info.KeyColumn + ": " + directValue + " - " + m_info);
}
catch (Exception e)
{
log.log(Level.SEVERE, m_info.KeyColumn + ": SQL=" + m_info.QueryDirect + "; Key=" + key, e);
directValue = null;
}
finally
{
DB.close(rs, pstmt);
rs = null;
pstmt = null;
}
// Cache Local if not added to R/W cache
if (cacheLocal && !saveInCache && directValue != null)
{
if (m_lookupDirect == null)
{
m_lookupDirect = new HashMap();
}
else if (!m_lookupDirect.containsKey(key))
{
m_lookupDirect.clear();
m_lookupDirect.put(key, directValue);
}
}
m_hasInactive = true;
return directValue;
} // getDirect
@Override
public NamePair[] getDirect(Object[] keys)
{
List list = new ArrayList();
String cacheKey = m_info.TableName+"|"+m_info.KeyColumn+"|"+m_info.AD_Reference_Value_ID+"|"+Env.getAD_Language(Env.getCtx());
boolean isNumber = m_info.KeyColumn.endsWith("_ID");
CCache knpCache = null;
CCache vnpCache = null;
Map notInCaches = new HashMap();
for (int i = 0; i < keys.length; i++)
{
Object key = keys[i];
if (isNumber)
{
KeyNamePair knp = null;
int id = Integer.parseInt(key.toString());
knpCache = getDirectKeyNamePairCache(m_info, cacheKey);
knp = knpCache.get(id);
if (knp == null)
{
knp = new KeyNamePair(id, null);
notInCaches.put(id, i);
}
list.add(knp);
}
else
{
ValueNamePair vnp = null;
vnpCache = getDirectValueNamePairCache(m_info, cacheKey);
vnp = vnpCache.get(key.toString());
if (vnp == null)
{
vnp = new ValueNamePair(key.toString(), null);
notInCaches.put(key.toString(), i);
}
list.add(vnp);
}
}
if (notInCaches.size() > 0)
{
StringBuilder builder = new StringBuilder();
for(int i = 0; i < notInCaches.size(); i++)
{
if (builder.length() > 0)
builder.append(" UNION ALL ");
builder.append(m_info.QueryDirect);
}
try (PreparedStatement pstmt = DB.prepareStatement(builder.toString(), null))
{
Set keySet = notInCaches.keySet();
int i = 0;
for(Object id : keySet)
{
i++;
if (id instanceof Integer)
{
pstmt.setInt(i, (int) id);
}
else
{
pstmt.setString(i, id.toString());
}
}
ResultSet rs = pstmt.executeQuery();
while (rs.next())
{
StringBuilder name = new StringBuilder().append(rs.getString(3));
boolean isActive = rs.getString(4).equals("Y");
if (!isActive)
{
name.insert(0, INACTIVE_S).append(INACTIVE_E);
}
if (isNumber)
{
int keyValue = rs.getInt(1);
KeyNamePair p = new KeyNamePair(keyValue, name.toString());
knpCache.put(p.getKey(), p);
Integer idx = notInCaches.get(p.getKey());
if (idx != null)
list.set(idx.intValue(), p);
}
else
{
String value;
if (m_info.KeyColumn.endsWith("_UU"))
value = rs.getString(1);
else
value = rs.getString(2);
ValueNamePair p = new ValueNamePair(value, name.toString());
vnpCache.put(p.getValue(), p);
Integer idx = notInCaches.get(p.getValue());
if (idx != null)
list.set(idx.intValue(), p);
}
}
} catch (SQLException e) {
log.log(Level.SEVERE, e.getMessage(), e);
}
for(int i = list.size()-1; i >= 0; i--)
{
NamePair np = list.get(i);
if (np.getName() == null)
list.remove(i);
}
}
return list.toArray(new NamePair[0]);
}
/**
* Get Zoom
* @return Zoom AD_Window_ID
*/
public int getZoom()
{
return m_info.ZoomWindow;
} // getZoom
/**
* Get Zoom
* @param query query
* @return Zoom Window
*/
@Override
public int getZoom(MQuery query)
{
if (m_info.ZoomWindowPO == 0 || query == null)
return m_info.ZoomWindow;
// Need to check SO/PO
boolean isSOTrx = DB.isSOTrx(m_info.TableName, query.getWhereClause(false), m_info.WindowNo);
//
return getZoom(isSOTrx);
} // getZoom
@Override
public int getZoom(boolean isSOTrx)
{
if (m_info.ZoomWindowPO == 0)
return m_info.ZoomWindow;
return isSOTrx ? m_info.ZoomWindow : m_info.ZoomWindowPO;
}
/**
* Get Zoom Query String
* @return Zoom SQL Where Clause
*/
@Override
public MQuery getZoomQuery()
{
return m_info.ZoomQuery;
} // getZoom
/**
* Get underlying fully qualified Table.Column Name
* @return Key Column
*/
@Override
public String getColumnName()
{
return m_info.KeyColumn;
} // g2etColumnName
/**
* Refresh and return number of items read.
* @return no of items read
*/
@Override
public int refresh ()
{
if (m_refreshing) return 0;
return refresh(true);
} // refresh
/**
* @return number of items read
*/
public int refreshItemsAndCache()
{
if (m_refreshing) return 0;
m_refreshCache = true;
try
{
return refresh();
}
finally
{
m_refreshCache = false;
}
}
/**
* Refresh and return number of items read
* @param loadParent true to load data of lookup with IsParent=true
* @return no of items refresh
*/
public int refresh (boolean loadParent)
{
if (m_refreshing) return 0;
if (!loadParent && m_info.IsParent)
return 0;
// Don't load Search or CreatedBy/UpdatedBy
if (m_info.DisplayType == DisplayType.Search
|| m_info.IsCreadedUpdatedBy)
{
//clear direct cache
removeAllElements();
return 0;
}
m_refreshing = true;
try
{
//force refresh
m_lookup.clear();
MReference ref = m_info.AD_Reference_Value_ID > 0 ? MReference.get(Env.getCtx(),m_info.AD_Reference_Value_ID) : null;
boolean onlyActive = ref == null || !ref.isShowInactiveRecords();
fillComboBox(isMandatory(), true, onlyActive, false, isShortList()); // idempiere 90
return m_lookup.size();
}
finally
{
m_refreshing = false;
}
} // refresh
/**
* Do the actual loading from database
* @param loadParent true to load data for lookup with IsParent=true
* @return number of records loaded
*/
private int loadData(boolean loadParent)
{
if (!loadParent && m_info.IsParent)
return 0;
// Don't load Search or CreatedBy/UpdatedBy
if (m_info.DisplayType == DisplayType.Search
|| m_info.IsCreadedUpdatedBy)
return 0;
if (log.isLoggable(Level.FINE)) log.fine(m_info.KeyColumn + ": start");
m_loader = new MLoader();
m_loaderFuture = Adempiere.getThreadPoolExecutor().submit(m_loader);
loadComplete();
if (log.isLoggable(Level.FINE)) log.fine(m_info.KeyColumn + ": #" + m_lookup.size());
return m_lookup.size();
} // refresh
/**
* Remove All cached Elements
* @see org.compiere.model.Lookup#removeAllElements()
*/
@Override
public void removeAllElements()
{
super.removeAllElements ();
m_lookup.clear();
if (m_lookupDirect != null)
m_lookupDirect.clear();
} // removeAllElements
/**
* @param info
* @return true if validation code have been parsed and doesn't need re-parse
*/
private boolean isValidated(MLookupInfo info)
{
if (info.IsValidated) return true;
if (info.ValidationCode.length() == 0) return true;
String validation = Env.parseContext(m_info.ctx, m_info.WindowNo, m_tabNo, m_info.ValidationCode, false);
if (validation.equals(info.parsedValidationCode)) return true;
return false;
}
/**
* @return lookup info
*/
public MLookupInfo getLookupInfo()
{
return m_info;
}
private final static CCache>> s_keyNamePairCache = new CCache>>(null, "MLookup.KeyNamePairCache", 100, CCache.DEFAULT_EXPIRE_MINUTE, false, 500);
private final static CCache>> s_valueNamePairCache = new CCache>>(null, "MLookup.ValueNamePairCache", 100, CCache.DEFAULT_EXPIRE_MINUTE, false, 500);
private final static CCache> s_directKeyNamePairCache = new CCache>(null, "MLookup.DirectKeyNamePairCache", 100, CCache.DEFAULT_EXPIRE_MINUTE, false, 500);
private final static CCache> s_directValueNamePairCache = new CCache>(null, "MLookup.DirectValueNamePairCache", 100, CCache.DEFAULT_EXPIRE_MINUTE, false, 500);
private synchronized static List getKeyNamePairCache(MLookupInfo lookupInfo, String cacheKey)
{
CCache> knpCache = s_keyNamePairCache.get(lookupInfo.TableName);
if (knpCache == null)
{
knpCache = new CCache>(lookupInfo.TableName, cacheKey + " KeyNamePair Cache", 100, CCache.DEFAULT_EXPIRE_MINUTE, false, 500);
s_keyNamePairCache.put(lookupInfo.TableName, knpCache);
}
List list = knpCache.get(cacheKey);
if (list == null)
{
list = new ArrayList();
knpCache.put(cacheKey, list);
}
return list;
}
private synchronized static List getValueNamePairCache(MLookupInfo lookupInfo, String cacheKey)
{
CCache> vnpCache = s_valueNamePairCache.get(lookupInfo.TableName);
if (vnpCache == null)
{
vnpCache = new CCache>(lookupInfo.TableName, cacheKey + " ValueNamePair Cache", 100, CCache.DEFAULT_EXPIRE_MINUTE, false, 500);
s_valueNamePairCache.put(lookupInfo.TableName, vnpCache);
}
List list = vnpCache.get(cacheKey);
if (list == null)
{
list = new ArrayList();
vnpCache.put(cacheKey, list);
}
return list;
}
private synchronized static CCache getDirectKeyNamePairCache(MLookupInfo lookupInfo, String cacheKey)
{
CCache knpCache = s_directKeyNamePairCache.get(cacheKey);
if (knpCache == null)
{
knpCache = new CCache(lookupInfo.TableName, cacheKey + " DirectKeyNamePairCache", 100, CCache.DEFAULT_EXPIRE_MINUTE, false, MAX_NAMEPAIR_CACHE_SIZE);
s_directKeyNamePairCache.put(cacheKey, knpCache);
}
return knpCache;
}
private synchronized static CCache getDirectValueNamePairCache(MLookupInfo lookupInfo, String cacheKey)
{
CCache vnpCache = s_directValueNamePairCache.get(cacheKey);
if (vnpCache == null)
{
vnpCache = new CCache(lookupInfo.TableName, cacheKey + " DirectValueNamePairCache", 100, CCache.DEFAULT_EXPIRE_MINUTE, false, MAX_NAMEPAIR_CACHE_SIZE);
s_directValueNamePairCache.put(cacheKey, vnpCache);
}
return vnpCache;
}
/**
* Get Lookup
* @param tableID
* @param windowNo
* @param tabNo
* @return null if tableID <= 0 or the table doesn't have any key column, else {@link MLookup}
*/
public static MLookup getRecordsLookup(int tableID, int windowNo, int tabNo) {
return getRecordsLookup(tableID, windowNo, tabNo, false);
}
/**
* Get Lookup
* @param tableID
* @param windowNo
* @param tabNo
* @param useUUIDKey - default false
* @return null if tableID <= 0 or the table doesn't have any key column, else {@link MLookup}
*/
public static MLookup getRecordsLookup(int tableID, int windowNo, int tabNo, boolean useUUIDKey) {
if(tableID <= 0)
return null;
MTable mTable = MTable.get(Env.getCtx(), tableID, null);
// load key column
String keyColumn = "";
if(!useUUIDKey) {
String[] keyColumns = mTable.getKeyColumns();
// the table has a single key column
if(keyColumns != null && keyColumns.length == 1)
keyColumn = keyColumns[0];
}
if(Util.isEmpty(keyColumn)) {
keyColumn = PO.getUUIDColumnName(mTable.getTableName());
}
if(Util.isEmpty(keyColumn))
return null;
MColumn mColumn = MColumn.get(Env.getCtx(), mTable.getTableName(), keyColumn);
MLookupInfo lookupInfo = MLookupFactory.getLookupInfo (Env.getCtx(), windowNo, tabNo, mColumn.getAD_Column_ID(), DisplayType.Search);
return new MLookup(lookupInfo, tabNo);
}
/**
* Get Identifier String from AD_Table_ID and Record_ID
* @param tableID
* @param recordID
* @return String
*/
public static String getIdentifier(int tableID, Serializable recordID) {
return getIdentifier(tableID, recordID, 0, 0);
}
/**
* Get Identifier String from AD_Table_ID and Record_ID
* @param tableID
* @param recordID
* @param windowNo
* @param tabNo
* @return String
*/
public static String getIdentifier(int tableID, Serializable recordID, int windowNo, int tabNo) {
return getIdentifier(tableID, recordID, windowNo, tabNo, false);
}
/**
* Get Identifier String from AD_Table_ID and Record_ID
* @param tableID
* @param recordID
* @param windowNo
* @param tabNo
* @param useUUIDKey - default false
* @return String
*/
public static String getIdentifier(int tableID, Serializable recordID, int windowNo, int tabNo, boolean useUUIDKey) {
MLookup lookup = getRecordsLookup(tableID, windowNo, tabNo, useUUIDKey);
return lookup != null ? lookup.getDisplay(recordID) : "";
}
/**
* Background Data Loader
*/
protected class MLoader extends ContextRunnable implements Serializable
{
/**
*
*/
private static final long serialVersionUID = -5752931726580011885L;
/**
* MLoader Constructor
*/
public MLoader()
{
super();
} // Loader
private long m_startTime = System.currentTimeMillis();
/**
* Load data
*/
protected void doRun()
{
/** Number of max rows to load */
int MAX_ROWS = MSysConfig.getIntValue(MSysConfig.MAX_ROWS_IN_TABLE_COMBOLIST, 10000, Env.getAD_Client_ID(Env.getCtx()));
if (MAX_ROWS > 50000) {
log.warning("SysConfig MAX_ROWS_IN_TABLE_COMBOLIST set back to maximum allowed value of 50.000");
MAX_ROWS = 50000; // impose hardcoded limit of 50.000
}
long startTime = System.currentTimeMillis();
StringBuilder sql = new StringBuilder().append(m_info.Query);
// IDEMPIERE 90
if (isShortList())
{
// Adding ", IsShortList" to the sql SELECT clause
int posFirstPoint = sql.indexOf(".");
String tableName = sql.substring(7, posFirstPoint);
int posFirstFrom = sql.indexOf(tableName+".IsActive FROM "+tableName) + tableName.length() + 9 ; // 9 = .IsActive
String ClauseFromWhereOrder = sql.substring(posFirstFrom, sql.length());
sql = new StringBuilder(sql.substring(0, posFirstFrom) + ", " + tableName + ".IsShortList" + ClauseFromWhereOrder);
} // IDEMPIERE 90
// not validated
if (!m_info.IsValidated)
{
String validation = Env.parseContext(m_info.ctx, m_info.WindowNo, m_tabNo, m_info.ValidationCode, false);
m_info.parsedValidationCode = validation;
if (validation.length() == 0 && m_info.ValidationCode.length() > 0)
{
if (log.isLoggable(Level.FINE)) log.fine(m_info.KeyColumn + ": Loader NOT Validated: " + m_info.ValidationCode);
// Bug 1843862 - Lookups not working on Report Viewer window
// globalqss - when called from Viewer window ignore error about not parseable context variables
// there is no context in report viewer windows
boolean isReportViewer = Env.getContext(m_info.ctx, m_info.WindowNo, "_WinInfo_IsReportViewer").equals("Y");
if (!isReportViewer) {
m_lookup.clear();
return;
}
}
else
{
if (log.isLoggable(Level.FINE)) log.fine(m_info.KeyColumn + ": Loader Validated: " + validation);
int posFrom = sql.lastIndexOf(" FROM ");
boolean hasWhere = sql.indexOf(" WHERE ", posFrom) != -1;
//
int posOrder = sql.lastIndexOf(" ORDER BY ");
if (posOrder != -1)
sql = new StringBuilder(sql.substring(0, posOrder))
.append((hasWhere ? " AND " : " WHERE "))
.append(validation)
.append(sql.substring(posOrder));
else
sql.append((hasWhere ? " AND " : " WHERE "))
.append(validation);
if (CLogMgt.isLevelFinest())
if (log.isLoggable(Level.FINE)) log.fine(m_info.KeyColumn + ": Validation=" + validation);
}
}
// check
if (Thread.interrupted())
{
log.log(Level.WARNING, m_info.KeyColumn + ": Loader interrupted");
return;
}
//
if (log.isLoggable(Level.FINER)) log.finer(m_info.Column_ID + ", " + m_info.KeyColumn + ": " + sql.toString());
if (log.isLoggable(Level.FINEST)) log.finest(m_info.KeyColumn + ": " + sql);
// Reset
m_lookup.clear();
boolean isNumber = m_info.KeyColumn.endsWith("_ID");
String cacheKey = sql.toString();
List knpCache = null;
List vnpCache = null;
if (isNumber)
{
knpCache = getKeyNamePairCache(m_info, cacheKey);
if (knpCache.size() > 0)
{
if (m_refreshCache)
{
knpCache.clear();
}
else
{
for(KeyNamePair knp : knpCache)
{
m_lookup.put(knp.getKey(), knp);
String name = knp.getName();
if (name.startsWith(INACTIVE_S) && name.endsWith(INACTIVE_E))
m_hasInactive = true;
}
return;
}
}
}
else
{
vnpCache = getValueNamePairCache(m_info, cacheKey);
if (vnpCache.size() > 0)
{
if (m_refreshCache)
{
vnpCache.clear();
}
else
{
for(ValueNamePair vnp : vnpCache)
{
m_lookup.put(vnp.getValue(), vnp);
String name = vnp.getName();
if (name.startsWith(INACTIVE_S) && name.endsWith(INACTIVE_E))
m_hasInactive = true;
}
return;
}
}
}
m_hasInactive = false;
int rows = 0;
PreparedStatement pstmt = null;
ResultSet rs = null;
try
{
// SELECT Key, Value, Name, IsActive FROM ...
String sqlFirstRows = DB.getDatabase().addPagingSQL(sql.toString(), 1, MAX_ROWS+1);
pstmt = DB.prepareStatement(sqlFirstRows, null);
if (! DB.getDatabase().isPagingSupported())
pstmt.setMaxRows(MAX_ROWS+1);
int timeout = MSysConfig.getIntValue(MSysConfig.GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS, GridTable.DEFAULT_GRIDTABLE_LOAD_TIMEOUT_IN_SECONDS, Env.getAD_Client_ID(Env.getCtx()));
if (timeout > 0)
pstmt.setQueryTimeout(timeout);
rs = pstmt.executeQuery();
// Get first ... rows
m_allLoaded = true;
while (rs.next())
{
if (rows++ > MAX_ROWS)
{
logLookup(Level.WARNING, "Too many records");
break;
}
// check for interrupted every 20 rows
if (rows % 20 == 0 && Thread.interrupted())
break;
// load data
StringBuilder name = new StringBuilder().append(rs.getString(3));
boolean isActive = rs.getString(4).equals("Y");
if (!isActive)
{
name.insert(0, INACTIVE_S).append(INACTIVE_E);
m_hasInactive = true;
}
// IDEMPIERE 90
if (isShortList())
{
boolean isShortListItem = rs.getString(5).equals("Y");
if (!isShortListItem)
{
name = new StringBuilder(SHORTLIST_S).append(name).append(SHORTLIST_S);
m_hasShortListItems = true;
}
}
// IDEMPIERE 90
if (isNumber)
{
int key = rs.getInt(1);
KeyNamePair p = new KeyNamePair(key, name.toString());
m_lookup.put(Integer.valueOf(key), p);
knpCache.add(p);
}
else
{
String value;
if (m_info.KeyColumn.endsWith("_UU"))
value = rs.getString(1);
else
value = rs.getString(2);
ValueNamePair p = new ValueNamePair(value, name.toString());
m_lookup.put(value, p);
vnpCache.add(p);
}
}
}
catch (SQLException e)
{
m_allLoaded = false;
if (DB.getDatabase().isQueryTimeout(e))
logLookup(Level.WARNING, "Too slow query");
else
logLookup(Level.SEVERE, e.getLocalizedMessage());
}
finally {
DB.close(rs, pstmt);
}
int size = m_lookup.size();
if (log.isLoggable(Level.FINER)) log.finer(m_info.KeyColumn
+ " (" + m_info.Column_ID + "):"
+ " - Loader complete #" + size + " - all=" + m_allLoaded
+ " - ms=" + String.valueOf(System.currentTimeMillis()-m_startTime)
+ " (" + String.valueOf(System.currentTimeMillis()-startTime) + ")");
} // run
/**
* Log a warning for the lookup problem found
* @param problem
*/
private void logLookup(Level level, String problem) {
if (log.isLoggable(level)) {
StringBuilder msg = new StringBuilder().append(m_info.KeyColumn).append(": Loader - ").append(problem);
if (m_info.Column_ID > 0) {
MColumn mColumn = MColumn.get(m_info.ctx, m_info.Column_ID);
String column = mColumn.getColumnName();
msg.append(", Column=").append(column);
String tableName = MTable.getTableName(m_info.ctx, mColumn.getAD_Table_ID());
msg.append(", Table=").append(tableName);
}
log.log(level, msg.toString());
}
}
} // Loader
} // MLookup