[ 2876263 ] 2pack: IDFinder should guarantee single result
https://sourceforge.net/tracker/?func=detail&aid=2876263&group_id=176962&atid=879335
This commit is contained in:
parent
854e299643
commit
f679050023
|
|
@ -13,33 +13,35 @@
|
||||||
*
|
*
|
||||||
* Copyright (C) 2005 Robert Klein. robeklein@hotmail.com
|
* Copyright (C) 2005 Robert Klein. robeklein@hotmail.com
|
||||||
* Contributor(s): Low Heng Sin hengsin@avantz.com
|
* Contributor(s): Low Heng Sin hengsin@avantz.com
|
||||||
|
* Teo Sarca, teo.sarca@gmail.com
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
package org.adempiere.pipo;
|
package org.adempiere.pipo;
|
||||||
|
|
||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
import org.adempiere.pipo.exception.DatabaseAccessException;
|
import org.adempiere.exceptions.DBException;
|
||||||
import org.compiere.util.CLogger;
|
import org.compiere.util.CLogger;
|
||||||
import org.compiere.util.DB;
|
import org.compiere.util.DB;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class for the looking up of record id.
|
* Utility class for the looking up of record id.
|
||||||
* @author Low Heng Sin
|
* @author Low Heng Sin
|
||||||
*
|
* @author Teo Sarca
|
||||||
*/
|
*/
|
||||||
public class IDFinder {
|
public final class IDFinder
|
||||||
|
{
|
||||||
private static CLogger log = CLogger.getCLogger(IDFinder.class);
|
private static CLogger log = CLogger.getCLogger(IDFinder.class);
|
||||||
|
|
||||||
private static Map<String, Integer>idCache = new HashMap<String, Integer>();
|
private static Map<String, Integer> idCache = new HashMap<String, Integer>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get ID from Name for a table.
|
* Get ID from Name for a table.
|
||||||
* TODO: substitute with PO.getAllIDs
|
|
||||||
*
|
*
|
||||||
* @param tableName
|
* @param tableName
|
||||||
* @param name
|
* @param name
|
||||||
|
|
@ -47,9 +49,8 @@ public class IDFinder {
|
||||||
* @param trxName
|
* @param trxName
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public static int get_ID (String tableName, String name, int AD_Client_ID, String trxName) {
|
public static int get_ID (String tableName, String name, int AD_Client_ID, String trxName)
|
||||||
int id = 0;
|
{
|
||||||
|
|
||||||
//construct cache key
|
//construct cache key
|
||||||
StringBuffer key = new StringBuffer();
|
StringBuffer key = new StringBuffer();
|
||||||
key.append(tableName)
|
key.append(tableName)
|
||||||
|
|
@ -61,37 +62,22 @@ public class IDFinder {
|
||||||
//check cache
|
//check cache
|
||||||
if (idCache.containsKey(key.toString()))
|
if (idCache.containsKey(key.toString()))
|
||||||
return idCache.get(key.toString());
|
return idCache.get(key.toString());
|
||||||
|
|
||||||
|
ArrayList<Object> params = new ArrayList<Object>();
|
||||||
StringBuffer sqlB = new StringBuffer ("select ")
|
StringBuffer sqlB = new StringBuffer ("select ")
|
||||||
.append(tableName)
|
.append(tableName)
|
||||||
.append("_ID from ")
|
.append("_ID from ")
|
||||||
.append(tableName)
|
.append(tableName)
|
||||||
.append(" where name=?");
|
.append(" where name=?");
|
||||||
|
params.add(name);
|
||||||
|
|
||||||
if (!tableName.startsWith("AD_"))
|
if (!tableName.startsWith("AD_"))
|
||||||
|
{
|
||||||
sqlB = sqlB.append(" and AD_Client_ID=?");
|
sqlB = sqlB.append(" and AD_Client_ID=?");
|
||||||
try {
|
params.add(AD_Client_ID);
|
||||||
PreparedStatement pstmt = DB.prepareStatement(sqlB.toString(), trxName);
|
|
||||||
pstmt.setString(1, name);
|
|
||||||
if (!tableName.startsWith("AD_"))
|
|
||||||
pstmt.setInt(2, AD_Client_ID);
|
|
||||||
ResultSet rs = pstmt.executeQuery();
|
|
||||||
if (rs.next())
|
|
||||||
id = rs.getInt(1);
|
|
||||||
rs.close();
|
|
||||||
pstmt.close();
|
|
||||||
pstmt = null;
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
log.info ("get_ID:"+e);
|
|
||||||
throw new DatabaseAccessException(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//keep in cache
|
return getID(sqlB.toString(), params, key.toString(), trxName);
|
||||||
if (id > 0)
|
|
||||||
idCache.put(key.toString(), id);
|
|
||||||
|
|
||||||
return id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -103,11 +89,10 @@ public class IDFinder {
|
||||||
* @param AD_Client_ID
|
* @param AD_Client_ID
|
||||||
* @param trxName
|
* @param trxName
|
||||||
*/
|
*/
|
||||||
public static int get_IDWithColumn (String tableName, String columnName, Object value, int AD_Client_ID, String trxName) {
|
public static int get_IDWithColumn (String tableName, String columnName, Object value, int AD_Client_ID, String trxName)
|
||||||
int id = 0;
|
{
|
||||||
|
|
||||||
if (value == null)
|
if (value == null)
|
||||||
return id;
|
return 0;
|
||||||
|
|
||||||
//construct cache key
|
//construct cache key
|
||||||
StringBuffer key = new StringBuffer();
|
StringBuffer key = new StringBuffer();
|
||||||
|
|
@ -119,10 +104,7 @@ public class IDFinder {
|
||||||
if (!tableName.startsWith("AD_"))
|
if (!tableName.startsWith("AD_"))
|
||||||
key.append(" and AD_Client_ID=").append(AD_Client_ID);
|
key.append(" and AD_Client_ID=").append(AD_Client_ID);
|
||||||
|
|
||||||
//check cache
|
ArrayList<Object> params = new ArrayList<Object>();
|
||||||
if (idCache.containsKey(key.toString()))
|
|
||||||
return idCache.get(key.toString());
|
|
||||||
|
|
||||||
StringBuffer sqlB = new StringBuffer ("select ")
|
StringBuffer sqlB = new StringBuffer ("select ")
|
||||||
.append(tableName)
|
.append(tableName)
|
||||||
.append("_ID from ")
|
.append("_ID from ")
|
||||||
|
|
@ -130,41 +112,19 @@ public class IDFinder {
|
||||||
.append(" where ")
|
.append(" where ")
|
||||||
.append(columnName)
|
.append(columnName)
|
||||||
.append(" = ?");
|
.append(" = ?");
|
||||||
|
params.add(value);
|
||||||
|
|
||||||
if (!tableName.startsWith("AD_"))
|
if (!tableName.startsWith("AD_"))
|
||||||
|
{
|
||||||
sqlB = sqlB.append(" and AD_Client_ID=?");
|
sqlB = sqlB.append(" and AD_Client_ID=?");
|
||||||
//here!
|
params.add(AD_Client_ID);
|
||||||
|
}
|
||||||
|
|
||||||
sqlB = sqlB.append(" Order By ")
|
sqlB = sqlB.append(" Order By ")
|
||||||
.append(tableName)
|
.append(tableName)
|
||||||
.append("_ID");
|
.append("_ID");
|
||||||
try {
|
|
||||||
PreparedStatement pstmt = DB.prepareStatement(sqlB.toString(), trxName);
|
|
||||||
if (value instanceof String)
|
|
||||||
pstmt.setString(1, (String)value);
|
|
||||||
else if (value instanceof Integer)
|
|
||||||
pstmt.setInt(1, ((Integer)value).intValue());
|
|
||||||
else
|
|
||||||
pstmt.setObject(1, value);
|
|
||||||
if (!tableName.startsWith("AD_"))
|
|
||||||
pstmt.setInt(2, AD_Client_ID);
|
|
||||||
|
|
||||||
ResultSet rs = pstmt.executeQuery();
|
|
||||||
if (rs.next())
|
|
||||||
id = rs.getInt(1);
|
|
||||||
rs.close();
|
|
||||||
pstmt.close();
|
|
||||||
pstmt = null;
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
log.info ("get_IDWithColumn:"+e);
|
|
||||||
throw new DatabaseAccessException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
//update cache
|
return getID(sqlB.toString(), params, key.toString(), trxName);
|
||||||
if (id > 0)
|
|
||||||
idCache.put(key.toString(), id);
|
|
||||||
|
|
||||||
return id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -176,8 +136,8 @@ public class IDFinder {
|
||||||
* @param nameMaster
|
* @param nameMaster
|
||||||
* @param trxName
|
* @param trxName
|
||||||
*/
|
*/
|
||||||
public static int get_IDWithMaster (String tableName, String name, String tableNameMaster, String nameMaster, String trxName) {
|
public static int get_IDWithMaster (String tableName, String name, String tableNameMaster, String nameMaster, String trxName)
|
||||||
int id = 0;
|
{
|
||||||
//construct cache key
|
//construct cache key
|
||||||
StringBuffer key = new StringBuffer();
|
StringBuffer key = new StringBuffer();
|
||||||
key.append(tableName)
|
key.append(tableName)
|
||||||
|
|
@ -188,10 +148,7 @@ public class IDFinder {
|
||||||
.append(".Name=")
|
.append(".Name=")
|
||||||
.append(nameMaster);
|
.append(nameMaster);
|
||||||
|
|
||||||
//check cache
|
ArrayList<Object> params = new ArrayList<Object>();
|
||||||
if (idCache.containsKey(key.toString()))
|
|
||||||
return idCache.get(key.toString());
|
|
||||||
|
|
||||||
StringBuffer sqlB = new StringBuffer ("select ")
|
StringBuffer sqlB = new StringBuffer ("select ")
|
||||||
.append(tableName)
|
.append(tableName)
|
||||||
.append("_ID from ")
|
.append("_ID from ")
|
||||||
|
|
@ -203,27 +160,10 @@ public class IDFinder {
|
||||||
.append("_ID from ")
|
.append("_ID from ")
|
||||||
.append(tableNameMaster)
|
.append(tableNameMaster)
|
||||||
.append(" where name = ?)");
|
.append(" where name = ?)");
|
||||||
|
params.add(name);
|
||||||
|
params.add(nameMaster);
|
||||||
|
|
||||||
try {
|
return getID(sqlB.toString(), params, key.toString(), trxName);
|
||||||
PreparedStatement pstmt = DB.prepareStatement(sqlB.toString(), trxName);
|
|
||||||
pstmt.setString(1, name);
|
|
||||||
pstmt.setString(2, nameMaster);
|
|
||||||
ResultSet rs = pstmt.executeQuery();
|
|
||||||
if (rs.next())
|
|
||||||
id = rs.getInt(1);
|
|
||||||
rs.close();
|
|
||||||
pstmt.close();
|
|
||||||
pstmt = null;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.info ("get_IDWithMaster:"+e);
|
|
||||||
throw new DatabaseAccessException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
//update cache
|
|
||||||
if (id > 0)
|
|
||||||
idCache.put(key.toString(), id);
|
|
||||||
|
|
||||||
return id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -235,16 +175,11 @@ public class IDFinder {
|
||||||
* @param masterID
|
* @param masterID
|
||||||
* @param trxName
|
* @param trxName
|
||||||
*/
|
*/
|
||||||
|
public static int get_IDWithMasterAndColumn (String tableName, String columnName, String name, String tableNameMaster, int masterID, String trxName)
|
||||||
public static int get_IDWithMasterAndColumn (String tableName, String columnName, String name, String tableNameMaster, int masterID, String trxName) {
|
{
|
||||||
int id = 0;
|
|
||||||
|
|
||||||
//check cache
|
|
||||||
String key = tableName + "." + columnName + "=" + name + tableNameMaster + "=" + masterID;
|
String key = tableName + "." + columnName + "=" + name + tableNameMaster + "=" + masterID;
|
||||||
|
|
||||||
if (idCache.containsKey(key))
|
ArrayList<Object> params = new ArrayList<Object>();
|
||||||
return idCache.get(key);
|
|
||||||
|
|
||||||
StringBuffer sqlB = new StringBuffer ("select ")
|
StringBuffer sqlB = new StringBuffer ("select ")
|
||||||
.append(tableName)
|
.append(tableName)
|
||||||
.append("_ID from ")
|
.append("_ID from ")
|
||||||
|
|
@ -253,34 +188,10 @@ public class IDFinder {
|
||||||
.append(columnName)
|
.append(columnName)
|
||||||
.append(" = ? and ")
|
.append(" = ? and ")
|
||||||
.append(tableNameMaster+"_ID =?");
|
.append(tableNameMaster+"_ID =?");
|
||||||
//StringBuffer sqlC = new StringBuffer ("select "+tableName+"_ID from "+tableName+" where "+columnName+"="+name+" and "
|
params.add(name);
|
||||||
// + tableNameMaster+"_ID ="+masterID);
|
params.add(masterID);
|
||||||
log.info(sqlB.toString());
|
|
||||||
|
|
||||||
try {
|
return getID(sqlB.toString(), params, key, trxName);
|
||||||
|
|
||||||
PreparedStatement pstmt = DB.prepareStatement(sqlB.toString(), trxName);
|
|
||||||
pstmt.setString(1, name);
|
|
||||||
pstmt.setInt(2, masterID);
|
|
||||||
ResultSet rs = pstmt.executeQuery();
|
|
||||||
if (rs.next())
|
|
||||||
{
|
|
||||||
id = rs.getInt(1);
|
|
||||||
}
|
|
||||||
rs.close();
|
|
||||||
pstmt.close();
|
|
||||||
pstmt = null;
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
log.info ("get_IDWithMasterAndColumn:"+e);
|
|
||||||
throw new DatabaseAccessException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
//update cache
|
|
||||||
if (id > 0)
|
|
||||||
idCache.put(key, id);
|
|
||||||
|
|
||||||
return id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -292,9 +203,8 @@ public class IDFinder {
|
||||||
* @param masterID
|
* @param masterID
|
||||||
* @param trxName
|
* @param trxName
|
||||||
*/
|
*/
|
||||||
public static int get_IDWithMaster (String tableName, String name, String tableNameMaster, int masterID, String trxName) {
|
public static int get_IDWithMaster (String tableName, String name, String tableNameMaster, int masterID, String trxName)
|
||||||
int id = 0;
|
{
|
||||||
|
|
||||||
//construct cache key
|
//construct cache key
|
||||||
StringBuffer key = new StringBuffer();
|
StringBuffer key = new StringBuffer();
|
||||||
key.append(tableName)
|
key.append(tableName)
|
||||||
|
|
@ -307,10 +217,7 @@ public class IDFinder {
|
||||||
.append("_ID=")
|
.append("_ID=")
|
||||||
.append(masterID);
|
.append(masterID);
|
||||||
|
|
||||||
//check cache
|
ArrayList<Object> params = new ArrayList<Object>();
|
||||||
if (idCache.containsKey(key.toString()))
|
|
||||||
return idCache.get(key.toString());
|
|
||||||
|
|
||||||
StringBuffer sqlB = new StringBuffer ("select ")
|
StringBuffer sqlB = new StringBuffer ("select ")
|
||||||
.append(tableName)
|
.append(tableName)
|
||||||
.append("_ID from ")
|
.append("_ID from ")
|
||||||
|
|
@ -318,33 +225,14 @@ public class IDFinder {
|
||||||
.append(" where name=? and ")
|
.append(" where name=? and ")
|
||||||
.append(tableNameMaster)
|
.append(tableNameMaster)
|
||||||
.append("_ID=?");
|
.append("_ID=?");
|
||||||
|
params.add(name);
|
||||||
|
params.add(masterID);
|
||||||
|
|
||||||
try {
|
return getID(sqlB.toString(), params, key.toString(), trxName);
|
||||||
PreparedStatement pstmt = DB.prepareStatement(sqlB.toString(), trxName);
|
|
||||||
pstmt.setString(1, name);
|
|
||||||
pstmt.setInt(2, masterID);
|
|
||||||
ResultSet rs = pstmt.executeQuery();
|
|
||||||
if (rs.next())
|
|
||||||
id = rs.getInt(1);
|
|
||||||
rs.close();
|
|
||||||
pstmt.close();
|
|
||||||
pstmt = null;
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
log.info ("get_IDWithMasterID:"+e);
|
|
||||||
throw new DatabaseAccessException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
//update cache
|
|
||||||
if (id > 0)
|
|
||||||
idCache.put(key.toString(), id);
|
|
||||||
|
|
||||||
return id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get ID from Column for a table.
|
* Get ID from Column for a table.
|
||||||
* TODO: substitute with PO.getAllIDs
|
|
||||||
*
|
*
|
||||||
* @param tableName
|
* @param tableName
|
||||||
* @param column
|
* @param column
|
||||||
|
|
@ -352,9 +240,8 @@ public class IDFinder {
|
||||||
* @param AD_Client_ID
|
* @param AD_Client_ID
|
||||||
* @param trxName
|
* @param trxName
|
||||||
*/
|
*/
|
||||||
public static int getIDbyColumn (String tableName, String column, String name, int AD_Client_ID, String trxName) {
|
public static int getIDbyColumn (String tableName, String column, String name, int AD_Client_ID, String trxName)
|
||||||
int id = 0;
|
{
|
||||||
|
|
||||||
//construct cache key
|
//construct cache key
|
||||||
StringBuffer key = new StringBuffer();
|
StringBuffer key = new StringBuffer();
|
||||||
key.append(tableName)
|
key.append(tableName)
|
||||||
|
|
@ -363,10 +250,7 @@ public class IDFinder {
|
||||||
if (!tableName.startsWith("AD_"))
|
if (!tableName.startsWith("AD_"))
|
||||||
key.append(" AND AD_Client_ID=").append(AD_Client_ID);
|
key.append(" AND AD_Client_ID=").append(AD_Client_ID);
|
||||||
|
|
||||||
//check cache
|
ArrayList<Object> params = new ArrayList<Object>();
|
||||||
if (idCache.containsKey(key.toString()))
|
|
||||||
return idCache.get(key.toString());
|
|
||||||
|
|
||||||
StringBuffer sql = new StringBuffer("SELECT ")
|
StringBuffer sql = new StringBuffer("SELECT ")
|
||||||
.append(tableName)
|
.append(tableName)
|
||||||
.append("_ID ")
|
.append("_ID ")
|
||||||
|
|
@ -374,43 +258,72 @@ public class IDFinder {
|
||||||
.append(tableName)
|
.append(tableName)
|
||||||
.append(" ")
|
.append(" ")
|
||||||
.append("WHERE "+column+"=?");
|
.append("WHERE "+column+"=?");
|
||||||
|
params.add(name);
|
||||||
if (!tableName.startsWith("AD_"))
|
if (!tableName.startsWith("AD_"))
|
||||||
|
{
|
||||||
sql.append(" AND AD_Client_ID=?");
|
sql.append(" AND AD_Client_ID=?");
|
||||||
try {
|
params.add(AD_Client_ID);
|
||||||
PreparedStatement pstmt = DB.prepareStatement(sql.toString(), trxName);
|
|
||||||
pstmt.setString(1, name);
|
|
||||||
if (!tableName.startsWith("AD_"))
|
|
||||||
pstmt.setInt(2, AD_Client_ID);
|
|
||||||
ResultSet rs = pstmt.executeQuery();
|
|
||||||
if (rs.next())
|
|
||||||
id = rs.getInt(1);
|
|
||||||
rs.close();
|
|
||||||
pstmt.close();
|
|
||||||
pstmt = null;
|
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
log.log(Level.SEVERE, "getIDbyColumn:"+e);
|
|
||||||
throw new DatabaseAccessException(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//update cache
|
return getID(sql.toString(), params, key.toString(), trxName);
|
||||||
if (id > 0)
|
|
||||||
idCache.put(key.toString(), id);
|
|
||||||
|
|
||||||
return id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getIDbyName (String tableName, String name, int AD_Client_ID, String trxName) {
|
public static int getIDbyName (String tableName, String name, int AD_Client_ID, String trxName)
|
||||||
|
{
|
||||||
return getIDbyColumn(tableName, "Name", name, AD_Client_ID, trxName);
|
return getIDbyColumn(tableName, "Name", name, AD_Client_ID, trxName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getIDbyValue (String tableName, String name, int AD_Client_ID, String trxName) {
|
public static int getIDbyValue (String tableName, String name, int AD_Client_ID, String trxName)
|
||||||
|
{
|
||||||
return getIDbyColumn(tableName, "Value", name, AD_Client_ID, trxName);
|
return getIDbyColumn(tableName, "Value", name, AD_Client_ID, trxName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void clearIDCache() {
|
public static void clearIDCache()
|
||||||
|
{
|
||||||
idCache.clear();
|
idCache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int getID(String sql, List<Object> params, String key, String trxName)
|
||||||
|
{
|
||||||
|
if (key != null && idCache.containsKey(key))
|
||||||
|
return idCache.get(key);
|
||||||
|
|
||||||
|
int id = 0;
|
||||||
|
PreparedStatement pstmt = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
pstmt = DB.prepareStatement(sql, trxName);
|
||||||
|
DB.setParameters(pstmt, params);
|
||||||
|
|
||||||
|
rs = pstmt.executeQuery();
|
||||||
|
if (rs.next())
|
||||||
|
{
|
||||||
|
id = rs.getInt(1);
|
||||||
|
}
|
||||||
|
if (rs.next())
|
||||||
|
{
|
||||||
|
log.warning("Not Unique ID found for key="+key);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (SQLException e)
|
||||||
|
{
|
||||||
|
throw new DBException(e, sql);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
DB.close(rs, pstmt);
|
||||||
|
rs = null; pstmt = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// update cache
|
||||||
|
if (key != null && id > 0)
|
||||||
|
{
|
||||||
|
idCache.put(key, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue