IDEMPIERE-312 Performance: Use JDK ThreadPool API for dynamically created thread
This commit is contained in:
parent
15a8173f54
commit
9d5f9f291b
|
@ -20,6 +20,7 @@
|
|||
package org.adempiere.base;
|
||||
|
||||
import org.adempiere.base.equinox.StackTraceCommand;
|
||||
import org.compiere.Adempiere;
|
||||
import org.eclipse.osgi.framework.console.CommandProvider;
|
||||
import org.osgi.framework.BundleActivator;
|
||||
import org.osgi.framework.BundleContext;
|
||||
|
@ -53,6 +54,7 @@ public class BaseActivator implements BundleActivator {
|
|||
@Override
|
||||
public void stop(BundleContext context) throws Exception {
|
||||
bundleContext = null;
|
||||
Adempiere.stop();
|
||||
}
|
||||
|
||||
public static BundleContext getBundleContext() {
|
||||
|
|
|
@ -23,6 +23,9 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import javax.jnlp.BasicService;
|
||||
|
@ -103,6 +106,9 @@ public final class Adempiere
|
|||
|
||||
/** Logging */
|
||||
private static CLogger log = null;
|
||||
|
||||
/** Thread pool **/
|
||||
private static ThreadPoolExecutor threadPoolExecutor = null;
|
||||
|
||||
static {
|
||||
ClassLoader loader = Adempiere.class.getClassLoader();
|
||||
|
@ -504,6 +510,22 @@ public final class Adempiere
|
|||
if (isClient && Ini.isPropertyBool(Ini.P_TRACEFILE))
|
||||
CLogMgt.addHandler(new CLogFile(Ini.findAdempiereHome(), true, isClient));
|
||||
|
||||
//setup specific log level
|
||||
Properties properties = Ini.getProperties();
|
||||
for(Object key : properties.keySet())
|
||||
{
|
||||
if (key instanceof String)
|
||||
{
|
||||
String s = (String)key;
|
||||
if (s.endsWith("."+Ini.P_TRACELEVEL))
|
||||
{
|
||||
String level = properties.getProperty(s);
|
||||
s = s.substring(0, s.length() - ("."+Ini.P_TRACELEVEL).length());
|
||||
CLogMgt.setLevel(s, level);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set UI
|
||||
if (isClient)
|
||||
{
|
||||
|
@ -514,12 +536,43 @@ public final class Adempiere
|
|||
// Set Default Database Connection from Ini
|
||||
DB.setDBTarget(CConnection.get(getCodeBaseHost()));
|
||||
|
||||
createThreadPool();
|
||||
|
||||
if (isClient) // don't test connection
|
||||
return false; // need to call
|
||||
|
||||
return startupEnvironment(isClient);
|
||||
} // startup
|
||||
|
||||
private static void createThreadPool() {
|
||||
int min = 20;
|
||||
int max = 200;
|
||||
Properties properties = Ini.getProperties();
|
||||
String maxSize = properties.getProperty("MaxThreadPoolSize");
|
||||
String minSize = properties.getProperty("MinThreadPoolSize");
|
||||
if (maxSize != null) {
|
||||
try {
|
||||
max = Integer.parseInt(maxSize);
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
if (minSize != null) {
|
||||
try {
|
||||
min = Integer.parseInt(minSize);
|
||||
} catch (Exception e) {}
|
||||
}
|
||||
if (max < min) {
|
||||
max = min;
|
||||
}
|
||||
if (max <= 0) {
|
||||
max = 200;
|
||||
}
|
||||
if (min < 0) {
|
||||
min = 20;
|
||||
}
|
||||
// start thread pool
|
||||
threadPoolExecutor = new ThreadPoolExecutor(min, max, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Startup Adempiere Environment.
|
||||
* Automatically called for Server connections
|
||||
|
@ -591,4 +644,14 @@ public final class Adempiere
|
|||
public static URL getResource(String name) {
|
||||
return Core.getResourceFinder().getResource(name);
|
||||
}
|
||||
|
||||
public static synchronized void stop() {
|
||||
if (threadPoolExecutor != null) {
|
||||
threadPoolExecutor.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
public static ThreadPoolExecutor getThreadPoolExecutor() {
|
||||
return threadPoolExecutor;
|
||||
}
|
||||
} // Adempiere
|
||||
|
|
|
@ -35,12 +35,14 @@ import java.util.LinkedHashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import javax.swing.event.TableModelListener;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
|
||||
import org.adempiere.exceptions.DBException;
|
||||
import org.compiere.Adempiere;
|
||||
import org.compiere.util.CLogMgt;
|
||||
import org.compiere.util.CLogger;
|
||||
import org.compiere.util.DB;
|
||||
|
@ -214,7 +216,7 @@ public class GridTable extends AbstractTableModel
|
|||
|
||||
/** Vetoable Change Bean support */
|
||||
private VetoableChangeSupport m_vetoableChangeSupport = new VetoableChangeSupport(this);
|
||||
private Thread m_loaderThread;
|
||||
private Future<?> m_loaderFuture;
|
||||
/** Property of Vetoable Bean support "RowChange" */
|
||||
public static final String PROPERTY = "MTable-RowSave";
|
||||
|
||||
|
@ -618,8 +620,7 @@ public class GridTable extends AbstractTableModel
|
|||
m_loader.run();
|
||||
else
|
||||
{
|
||||
m_loaderThread = new Thread(m_loader, "TLoader");
|
||||
m_loaderThread.start();
|
||||
m_loaderFuture = Adempiere.getThreadPoolExecutor().submit(m_loader);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -656,17 +657,17 @@ public class GridTable extends AbstractTableModel
|
|||
public void loadComplete()
|
||||
{
|
||||
// Wait for loader
|
||||
if (m_loaderThread != null)
|
||||
if (m_loaderFuture != null)
|
||||
{
|
||||
if (m_loaderThread.isAlive())
|
||||
if (!m_loaderFuture.isDone())
|
||||
{
|
||||
try
|
||||
{
|
||||
m_loaderThread.join();
|
||||
m_loaderFuture.get();
|
||||
}
|
||||
catch (InterruptedException ie)
|
||||
catch (Exception ie)
|
||||
{
|
||||
log.log(Level.SEVERE, "Join interrupted", ie);
|
||||
log.log(Level.SEVERE, "Interrupted", ie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -684,7 +685,7 @@ public class GridTable extends AbstractTableModel
|
|||
*/
|
||||
public boolean isLoading()
|
||||
{
|
||||
if (m_loaderThread != null && m_loaderThread.isAlive())
|
||||
if (m_loaderFuture != null && !m_loaderFuture.isDone())
|
||||
return true;
|
||||
return false;
|
||||
} // isLoading
|
||||
|
@ -723,16 +724,17 @@ public class GridTable extends AbstractTableModel
|
|||
}
|
||||
|
||||
// Stop loader
|
||||
while (m_loaderThread != null && m_loaderThread.isAlive())
|
||||
while (m_loaderFuture != null && !m_loaderFuture.isDone())
|
||||
{
|
||||
log.fine("Interrupting Loader ...");
|
||||
m_loaderThread.interrupt();
|
||||
m_loaderFuture.cancel(true);
|
||||
try
|
||||
{
|
||||
Thread.sleep(200); // .2 second
|
||||
}
|
||||
catch (InterruptedException ie)
|
||||
{}
|
||||
m_loaderFuture = null;
|
||||
}
|
||||
|
||||
if (!m_inserting)
|
||||
|
@ -787,7 +789,7 @@ public class GridTable extends AbstractTableModel
|
|||
m_rowData = null;
|
||||
m_oldValue = null;
|
||||
m_loader = null;
|
||||
m_loaderThread = null;
|
||||
m_loaderFuture = null;
|
||||
} // dispose
|
||||
|
||||
/**
|
||||
|
@ -985,7 +987,7 @@ public class GridTable extends AbstractTableModel
|
|||
|
||||
// need to wait for data read into buffer
|
||||
int loops = 0;
|
||||
while (row >= m_sort.size() && m_loaderThread != null && m_loaderThread.isAlive() && loops < 15)
|
||||
while (row >= m_sort.size() && m_loaderFuture != null && !m_loaderFuture.isDone() && loops < 15)
|
||||
{
|
||||
log.fine("Waiting for loader row=" + row + ", size=" + m_sort.size());
|
||||
try
|
||||
|
|
|
@ -23,8 +23,10 @@ import java.sql.SQLException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.compiere.Adempiere;
|
||||
import org.compiere.util.CLogMgt;
|
||||
import org.compiere.util.DB;
|
||||
import org.compiere.util.DisplayType;
|
||||
|
@ -67,8 +69,11 @@ public final class MLookup extends Lookup implements Serializable
|
|||
log.fine(m_info.KeyColumn);
|
||||
|
||||
// load into local lookup, if already cached
|
||||
if (MLookupCache.loadFromCache (m_info, m_lookup))
|
||||
return;
|
||||
if (Ini.isClient())
|
||||
{
|
||||
if (MLookupCache.loadFromCache (m_info, m_lookup))
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't load Search or CreatedBy/UpdatedBy
|
||||
if (m_info.DisplayType == DisplayType.Search
|
||||
|
@ -124,9 +129,10 @@ public final class MLookup extends Lookup implements Serializable
|
|||
{
|
||||
if (m_info != null)
|
||||
log.fine(m_info.KeyColumn + ": dispose");
|
||||
if (m_loader != null && m_loader.isAlive())
|
||||
m_loader.interrupt();
|
||||
if (m_loaderFuture != null && !m_loaderFuture.isDone())
|
||||
m_loaderFuture.cancel(true);
|
||||
m_loader = null;
|
||||
m_loaderFuture = null;
|
||||
//
|
||||
if (m_lookup != null)
|
||||
m_lookup.clear();
|
||||
|
@ -145,17 +151,18 @@ public final class MLookup extends Lookup implements Serializable
|
|||
*/
|
||||
public void loadComplete()
|
||||
{
|
||||
if (m_loader != null && m_loader.isAlive())
|
||||
if (m_loaderFuture != null && !m_loaderFuture.isDone())
|
||||
{
|
||||
try
|
||||
try
|
||||
{
|
||||
m_loader.join();
|
||||
m_loader = null;
|
||||
m_loaderFuture.get();
|
||||
}
|
||||
catch (InterruptedException ie)
|
||||
catch (Exception ie)
|
||||
{
|
||||
log.log(Level.SEVERE, m_info.KeyColumn + ": Interrupted", ie);
|
||||
}
|
||||
}
|
||||
m_loader = null;
|
||||
m_loaderFuture = null;
|
||||
}
|
||||
} // loadComplete
|
||||
|
||||
|
@ -188,7 +195,7 @@ public final class MLookup extends Lookup implements Serializable
|
|||
return retValue;
|
||||
|
||||
// Not found and waiting for loader
|
||||
if (m_loader != null && m_loader.isAlive())
|
||||
if (m_loaderFuture != null && !m_loaderFuture.isDone())
|
||||
{
|
||||
log.finer((m_info.KeyColumn==null ? "ID="+m_info.Column_ID : m_info.KeyColumn) + ": waiting for Loader");
|
||||
loadComplete();
|
||||
|
@ -332,7 +339,7 @@ public final class MLookup extends Lookup implements Serializable
|
|||
*/
|
||||
private ArrayList<Object> getData (boolean onlyValidated, boolean loadParent)
|
||||
{
|
||||
if (m_loader != null && m_loader.isAlive())
|
||||
if (m_loaderFuture != null && !m_loaderFuture.isDone())
|
||||
{
|
||||
log.fine((m_info.KeyColumn==null ? "ID="+m_info.Column_ID : m_info.KeyColumn)
|
||||
+ ": waiting for Loader");
|
||||
|
@ -407,6 +414,7 @@ public final class MLookup extends Lookup implements Serializable
|
|||
private HashMap<Object,Object> m_lookupDirect = null;
|
||||
/** Save last unsuccessful */
|
||||
private Object m_directNullKey = null;
|
||||
private Future<?> m_loaderFuture;
|
||||
|
||||
/**
|
||||
* Get Data Direct from Table.
|
||||
|
@ -592,7 +600,7 @@ public final class MLookup extends Lookup implements Serializable
|
|||
log.fine(m_info.KeyColumn + ": start");
|
||||
|
||||
m_loader = new MLoader();
|
||||
m_loader.start();
|
||||
m_loaderFuture = Adempiere.getThreadPoolExecutor().submit(m_loader);
|
||||
loadComplete();
|
||||
log.fine(m_info.KeyColumn + ": #" + m_lookup.size());
|
||||
|
||||
|
@ -631,7 +639,7 @@ public final class MLookup extends Lookup implements Serializable
|
|||
/**************************************************************************
|
||||
* MLookup Loader
|
||||
*/
|
||||
class MLoader extends Thread implements Serializable
|
||||
class MLoader implements Serializable, Runnable
|
||||
{
|
||||
/**
|
||||
*
|
||||
|
@ -643,9 +651,6 @@ public final class MLookup extends Lookup implements Serializable
|
|||
*/
|
||||
public MLoader()
|
||||
{
|
||||
super("MLoader-" + m_info.KeyColumn);
|
||||
// if (m_info.KeyColumn.indexOf("C_InvoiceLine_ID") != -1)
|
||||
// log.info(m_info.KeyColumn);
|
||||
} // Loader
|
||||
|
||||
private long m_startTime = System.currentTimeMillis();
|
||||
|
@ -656,7 +661,8 @@ public final class MLookup extends Lookup implements Serializable
|
|||
public void run()
|
||||
{
|
||||
long startTime = System.currentTimeMillis();
|
||||
MLookupCache.loadStart (m_info);
|
||||
if (Ini.isClient())
|
||||
MLookupCache.loadStart (m_info);
|
||||
String sql = m_info.Query;
|
||||
|
||||
// not validated
|
||||
|
@ -697,7 +703,7 @@ public final class MLookup extends Lookup implements Serializable
|
|||
}
|
||||
}
|
||||
// check
|
||||
if (isInterrupted())
|
||||
if (Thread.interrupted())
|
||||
{
|
||||
log.log(Level.WARNING, m_info.KeyColumn + ": Loader interrupted");
|
||||
return;
|
||||
|
@ -727,11 +733,20 @@ public final class MLookup extends Lookup implements Serializable
|
|||
{
|
||||
if (rows++ > MAX_ROWS)
|
||||
{
|
||||
log.warning(m_info.KeyColumn + ": Loader - Too many records");
|
||||
String s = m_info.KeyColumn + ": Loader - Too many records";
|
||||
if (m_info.Column_ID > 0)
|
||||
{
|
||||
MColumn mColumn = MColumn.get(m_info.ctx, m_info.Column_ID);
|
||||
String column = mColumn.getColumnName();
|
||||
s = s + ", Column="+column;
|
||||
String tableName = MTable.getTableName(m_info.ctx, mColumn.getAD_Table_ID());
|
||||
s = s + ", Table="+tableName;
|
||||
}
|
||||
log.warning(s);
|
||||
break;
|
||||
}
|
||||
// check for interrupted every 10 rows
|
||||
if (rows % 20 == 0 && isInterrupted())
|
||||
if (rows % 20 == 0 && Thread.interrupted())
|
||||
break;
|
||||
|
||||
// load data
|
||||
|
@ -773,7 +788,8 @@ public final class MLookup extends Lookup implements Serializable
|
|||
+ " - ms=" + String.valueOf(System.currentTimeMillis()-m_startTime)
|
||||
+ " (" + String.valueOf(System.currentTimeMillis()-startTime) + ")");
|
||||
// if (m_allLoaded)
|
||||
MLookupCache.loadEnd (m_info, m_lookup);
|
||||
if (Ini.isClient())
|
||||
MLookupCache.loadEnd (m_info, m_lookup);
|
||||
} // run
|
||||
} // Loader
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import java.sql.SQLException;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.adempiere.util.IProcessMonitor;
|
||||
|
@ -22,6 +23,7 @@ import org.adempiere.webui.process.WProcessInfo;
|
|||
import org.adempiere.webui.session.SessionManager;
|
||||
import org.adempiere.webui.window.FDialog;
|
||||
import org.adempiere.webui.window.SimplePDFViewer;
|
||||
import org.compiere.Adempiere;
|
||||
import org.compiere.model.SystemIDs;
|
||||
import org.compiere.print.ReportEngine;
|
||||
import org.compiere.process.ProcessInfo;
|
||||
|
@ -90,7 +92,7 @@ public class ProcessDialog extends Window implements EventListener<Event>, IProc
|
|||
private Center center;
|
||||
private North north;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Dialog to start a process/report
|
||||
* @param ctx
|
||||
|
@ -197,7 +199,8 @@ public class ProcessDialog extends Window implements EventListener<Event>, IProc
|
|||
private boolean isParameterPage = true;
|
||||
private String initialMessage;
|
||||
private BusyDialog progressWindow;
|
||||
private Thread thread;
|
||||
@SuppressWarnings("unused")
|
||||
private Future<?> future;
|
||||
private ProcessDialogRunnable processDialogRunnable;
|
||||
|
||||
private static final String ON_STATUS_UPDATE = "onStatusUpdate";
|
||||
|
@ -333,13 +336,12 @@ public class ProcessDialog extends Window implements EventListener<Event>, IProc
|
|||
p.put(AdempiereWebUI.ZK_DESKTOP_SESSION_KEY, desktop);
|
||||
|
||||
processDialogRunnable = new ProcessDialogRunnable(p);
|
||||
thread = new Thread(processDialogRunnable);
|
||||
thread.start();
|
||||
future = Adempiere.getThreadPoolExecutor().submit(processDialogRunnable);
|
||||
}
|
||||
|
||||
private void onComplete() {
|
||||
Env.getCtx().putAll(processDialogRunnable.getProperties());
|
||||
thread = null;
|
||||
future = null;
|
||||
processDialogRunnable = null;
|
||||
unlockUI(m_pi);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import java.sql.PreparedStatement;
|
|||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.adempiere.util.IProcessMonitor;
|
||||
|
@ -31,6 +32,7 @@ import org.adempiere.webui.component.Panel;
|
|||
import org.adempiere.webui.component.VerticalBox;
|
||||
import org.adempiere.webui.component.Window;
|
||||
import org.adempiere.webui.event.DialogEvents;
|
||||
import org.compiere.Adempiere;
|
||||
import org.compiere.process.ProcessInfo;
|
||||
import org.compiere.util.CLogger;
|
||||
import org.compiere.util.DB;
|
||||
|
@ -70,7 +72,7 @@ public class ProcessModalDialog extends Window implements EventListener<Event>,
|
|||
private static final long serialVersionUID = -7109707014309321369L;
|
||||
private boolean m_autoStart;
|
||||
private VerticalBox dialogBody;
|
||||
|
||||
|
||||
/**
|
||||
* @param aProcess
|
||||
* @param WindowNo
|
||||
|
@ -84,7 +86,7 @@ public class ProcessModalDialog extends Window implements EventListener<Event>,
|
|||
m_WindowNo = WindowNo;
|
||||
m_pi = pi;
|
||||
m_autoStart = autoStart;
|
||||
|
||||
|
||||
log.info("Process=" + pi.getAD_Process_ID());
|
||||
try
|
||||
{
|
||||
|
@ -143,9 +145,11 @@ public class ProcessModalDialog extends Window implements EventListener<Event>,
|
|||
dialogBody.appendChild(div);
|
||||
centerPanel = new Panel();
|
||||
dialogBody.appendChild(centerPanel);
|
||||
div = new Div();
|
||||
div.setStyle("text-align: right");
|
||||
// div = new Div();
|
||||
// div.setStyle("text-align: right");
|
||||
Hbox hbox = new Hbox();
|
||||
hbox.setWidth("100%");
|
||||
hbox.setStyle("margin-top: 10px");
|
||||
Button btn = new Button("Ok");
|
||||
LayoutUtils.addSclass("action-text-button", btn);
|
||||
btn.setId("Ok");
|
||||
|
@ -158,8 +162,9 @@ public class ProcessModalDialog extends Window implements EventListener<Event>,
|
|||
btn.addEventListener(Events.ON_CLICK, this);
|
||||
|
||||
hbox.appendChild(btn);
|
||||
div.appendChild(hbox);
|
||||
dialogBody.appendChild(div);
|
||||
hbox.setPack("end");
|
||||
// div.appendChild(hbox);
|
||||
dialogBody.appendChild(hbox);
|
||||
this.appendChild(dialogBody);
|
||||
|
||||
}
|
||||
|
@ -185,7 +190,8 @@ public class ProcessModalDialog extends Window implements EventListener<Event>,
|
|||
private BusyDialog progressWindow;
|
||||
private boolean isLocked = false;
|
||||
private org.adempiere.webui.apps.ProcessModalDialog.ProcessDialogRunnable processDialogRunnable;
|
||||
private Thread thread;
|
||||
@SuppressWarnings("unused")
|
||||
private Future<?> future;
|
||||
|
||||
/**
|
||||
* Set Visible
|
||||
|
@ -317,14 +323,14 @@ public class ProcessModalDialog extends Window implements EventListener<Event>,
|
|||
* launch process
|
||||
*/
|
||||
private void startProcess()
|
||||
{
|
||||
{
|
||||
m_pi.setPrintPreview(true);
|
||||
|
||||
if (m_processMonitor != null) {
|
||||
m_processMonitor.lockUI(m_pi);
|
||||
Clients.clearBusy();
|
||||
}
|
||||
|
||||
|
||||
lockUI(m_pi);
|
||||
|
||||
//use echo, otherwise lock ui wouldn't work
|
||||
|
@ -361,8 +367,7 @@ public class ProcessModalDialog extends Window implements EventListener<Event>,
|
|||
p.put(AdempiereWebUI.ZK_DESKTOP_SESSION_KEY, desktop);
|
||||
|
||||
processDialogRunnable = new ProcessDialogRunnable(p);
|
||||
thread = new Thread(processDialogRunnable);
|
||||
thread.start();
|
||||
future = Adempiere.getThreadPoolExecutor().submit(processDialogRunnable);
|
||||
}
|
||||
|
||||
private void hideBusyDialog() {
|
||||
|
@ -409,14 +414,14 @@ public class ProcessModalDialog extends Window implements EventListener<Event>,
|
|||
|
||||
private void onComplete() {
|
||||
Env.getCtx().putAll(processDialogRunnable.getProperties());
|
||||
thread = null;
|
||||
future = null;
|
||||
processDialogRunnable = null;
|
||||
dispose();
|
||||
dispose();
|
||||
if (m_processMonitor != null) {
|
||||
m_processMonitor.unlockUI(m_pi);
|
||||
}
|
||||
unlockUI(m_pi);
|
||||
Events.sendEvent(this, new Event(ON_MODAL_CLOSE, this, null));
|
||||
Events.sendEvent(this, new Event(ON_MODAL_CLOSE, this, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
Loading…
Reference in New Issue