I am trying to implement a JMXclient to monitor remote JVM. I obtain the mbeanserver connection from a main method and pass it to a thread which search for memory usage. Problem is when I do this i get an error saying "The client has been closed". But if I run the program without threads it works perfectly.
public class ESBMonitor {
private static TreeSet<ObjectName> mbeansNames = null;
public static void main(String[] args) throws IOException {
RemoteConnector.defaultConnector();
MBeanServerConnection remote = RemoteConnector.getRemote();
//MemoryExtractor.getMemoryInfo();
MemoryExtractor memoryExtractor = new MemoryExtractor();
memoryExtractor.start();
RemoteConnector.closeConnection();
}
}
public class RemoteConnector {
private static MBeanServerConnection remote = null;
private static JMXConnector connector = null;
private static final Logger logger= LogManager.getLogger(RemoteConnector.class);
public static void defaultConnector() {
try {
JMXServiceURL target = new JMXServiceURL
("service:jmx:rmi://localhost:11111/jndi/rmi://localhost:9999/jmxrmi");
//for passing credentials for password
Map<String, String[]> env = new HashMap<String, String[]>();
String[] credentials = {"admin", "admin"};
env.put(JMXConnector.CREDENTIALS, credentials);
connector = JMXConnectorFactory.connect(target, env);
remote = connector.getMBeanServerConnection();
logger.info("MbeanServer connection obtained");
} catch (MalformedURLException e) {
logger.error(e.getStackTrace());
} catch (IOException e) {
logger.error(e.getStackTrace());
}
}
public static MBeanServerConnection getRemote() {
return remote;
}
public static synchronized Object getMbeanAttribute(ObjectName bean, String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException, InstanceNotFoundException, IOException {
return remote.getAttribute(bean,attribute);
}
}
public class MemoryExtractor extends Thread{
final static Logger logger = Logger.getLogger(MemoryExtractor.class);
private static MBeanInfo memoryInfo;
private static ObjectName bean = null;
private static double MEMORY = 0.05;
private static long TIMEOUT = 3000;
public static void getMemoryInfo() {
try {
bean = new ObjectName("java.lang:type=Memory");
checkWarningUsage();
} catch (MalformedObjectNameException e) {
logger.error("MemoryExtractor.java:25 " + e.getMessage());
}
}
public static boolean checkWarningUsage() {
try {
logger.info("MemoryExtractor.java:46 Acessing memory details");
CompositeData memoryUsage = (CompositeData) RemoteConnector.getMbeanAttribute(bean,"HeapMemoryUsage");
} catch (Exception e) {
logger.error("MemoryExtractor.java:58 " + e.getMessage());
}
return false;
}
public void run(){
while (true){
getMemoryInfo();
}
}
}
No matter I synchronize or not problem will still be there.
Stack trace
java.io.IOException: The client has been closed.
at com.sun.jmx.remote.internal.ClientCommunicatorAdmin.restart(ClientCommunicatorAdmin.java:94)
at com.sun.jmx.remote.internal.ClientCommunicatorAdmin.gotIOException(ClientCommunicatorAdmin.java:54)
at javax.management.remote.rmi.RMIConnector$RMIClientCommunicatorAdmin.gotIOException(RMIConnector.java:1474)
at javax.management.remote.rmi.RMIConnector$RemoteMBeanServerConnection.getAttribute(RMIConnector.java:910)
at org.wso2.connector.RemoteConnector.getMbeanAttribute(RemoteConnector.java:55)
at org.wso2.jvmDetails.MemoryExtractor.checkWarningUsage(MemoryExtractor.java:47)
at org.wso2.jvmDetails.MemoryExtractor.run(MemoryExtractor.java:79)
You are caching the reference to the JMXConnector as a static in your RemoteConnector class, so there's only ever one connection. As soon as the first thread closes that singleton connection, the next time any other thread attempts to call something it will fail because you've already closed the connection at that point.
If you want to have multiple threads, then either you should close the connection when all threads have finished, or have one connection per thread.
Related
I have this class (my database connection factory):
#ApplicationScoped
public class ConnectionFactory {
private ComboPooledDataSource datasource;
private Long open = 0l;
private Long close = 0l;
#PostConstruct
public void init() throws PropertyVetoException, SQLException {
datasource = new ComboPooledDataSource();
datasource.setDriverClass("org.postgresql.Driver");
datasource.setJdbcUrl("jdbc:postgresql:dbcampanha");
datasource.setUser("postgres");
datasource.setPassword("admin");
datasource.setMinPoolSize(1);
datasource.setMaxPoolSize(5);
datasource.setCheckoutTimeout(30000);
datasource.setUnreturnedConnectionTimeout(30);
datasource.setMaxIdleTime(30);
datasource.setDebugUnreturnedConnectionStackTraces(true);
datasource.setAcquireIncrement(1);
}
#Produces
#RequestScoped
public Connection getConnection() throws ClassNotFoundException {
open++;
try {
Connection connection = datasource.getConnection();
connection.setAutoCommit(false);
return connection;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public void close(#Disposes Connection connection) {
close++;
try {
connection.commit();
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public Long getOpenedConnectionCounter() {
return open;
}
public Long getClosedConnectionCounter(){
return close;
}
public ComboPooledDataSource getDatasource(){
return datasource;
}
}
I use this class with an JAX-RS application. And for some tests using this route:
#RequestScoped
#Path("/test")
public class TesteService {
#Inject
private Connection connection;
#GET
#Produces(MyMediaType.JSON)
#Path("/yes")
public Response success() throws SQLException {
connection.getClientInfo("");
return Response.ok().build();
}
}
And this class for my Client:
public class TesteMain {
private static final String prefix = "http://localhost:8080/schoolwork/service/test/";
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10000; i++) {
Request request = new Request(prefix + "yes");
request.start();
if(i % 10 == 0)
Thread.sleep(1000l);
}
}
public static class Request extends Thread {
private String rota;
public Request(String rota){
this.rota = rota;
}
#Override
public void run() {
try {
HttpURLConnection url = (HttpURLConnection) (new URL(rota).openConnection());
url.connect();
System.out.println(url.getResponseCode() == 200 ? "SUCCESS" : "ERROR");
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
I receive this infos:
{
"opened-connection": 789,
"closed-connection": 867,
}
Yes, I have a number of closed database connection greater than opened. HOW? Any idea for this?
I use
Tomcat 7 + Java 7
P.S. I am sorry for my bad English :/
SOLVED
I change my counters for AtomicInteger objects, and works perfectly.
So, two quick comments:
Your counters are (boxed) longs read and updated willy-nilly by 1000 concurrent Threads. Their values will in general be unpredictable and nondeterministic. They certainly won't accurately count what you intend them to count. Consider using atomic operations on AtomicLongs instead.
Your commit() (or rollback()) should be attached to your database business logic, the part where you can tell a unit of work has either succeeded or failed. You shouldn't automatically commit on close.
I wrote an QuickFix program and created a config file.. Now, when I'm running my application, nothing happens.. So I ask me, how I can recognize if the connection (my program is of type initiator) is established or not.
I've added to the methods that I implement from the Application interface
void fromAdmin(Message message, SessionID sessionId)
void fromApp(Message message, SessionID sessionId)
void onCreate(SessionID sessionId)
void onLogon(SessionID sessionId)
void onLogout(SessionID sessionId)
void toAdmin(Message message, SessionID sessionId)
void toApp(Message message, SessionID sessionId)
a System.out and a logger.info, but nothing of them fires something.
If a connection established, the methods onCreate and onLogon are invoked, or?! So the System.outs in these methods should write something..
But are there any other opportunitys to check wheter the connection established, respectively the config file is valid.
P.S.: I use the SessionSettings to read the config File in.. But I can't find a method in the SessionSettings like validateConfigFile() or something like that.
Has your config file got FileLogPath=log?
Then to debug you look at the FIX messages log, usually in the bin/debug/log directory, which is where it would be for the above config file. If it's not a security risk then paste your config file here please. Also, yes add the System.Out to your Application interface. Here's mine:
public void FromApp(Message msg, SessionID s)
{
Console.WriteLine("IN: " + msg.ToString());
You can download Banzai application which is an accpetor/iniciator with grafic interface. You can iniciate a session, and send/receive messages from your app.
Main class you need in your Iniciator
public class Banzai {
private static final CountDownLatch shutdownLatch = new CountDownLatch(1);
/** enable logging for this class */
private static Logger log = LoggerFactory.getLogger(Banzai.class);
private static Banzai banzai;
private boolean initiatorStarted = false;
private Initiator initiator = null;
private JFrame frame = null;
public Banzai(String[] args) throws Exception {
InputStream inputStream = null;
if (args.length == 0) {
inputStream = new BufferedInputStream(new FileInputStream(new File("config/banzai.cfg")));
} else if (args.length == 1) {
inputStream = new FileInputStream(args[0]);
}
if (inputStream == null) {
System.out.println("usage: " + Banzai.class.getName() + " [configFile].");
return;
}
SessionSettings settings = new SessionSettings(inputStream);
inputStream.close();
boolean logHeartbeats = Boolean.valueOf(System.getProperty("logHeartbeats", "true")).booleanValue();
OrderTableModel orderTableModel = new OrderTableModel();
ExecutionTableModel executionTableModel = new ExecutionTableModel();
BanzaiApplication application = new BanzaiApplication(orderTableModel, executionTableModel);
MessageStoreFactory messageStoreFactory = new FileStoreFactory(settings);
LogFactory logFactory = new ScreenLogFactory(true, true, true, logHeartbeats);
MessageFactory messageFactory = new DefaultMessageFactory();
initiator = new SocketInitiator(application, messageStoreFactory, settings, logFactory, messageFactory);
frame = new BanzaiFrame(orderTableModel, executionTableModel, application);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public synchronized void logon() {
if (!initiatorStarted) {
try {
initiator.start();
initiatorStarted = true;
} catch (Exception e) {
log.error("Logon failed", e);
}
} else {
Iterator<SessionID> sessionIds = initiator.getSessions().iterator();
while (sessionIds.hasNext()) {
SessionID sessionId = (SessionID) sessionIds.next();
Session.lookupSession(sessionId).logon();
}
}
}
public void logout() {
Iterator<SessionID> sessionIds = initiator.getSessions().iterator();
while (sessionIds.hasNext()) {
SessionID sessionId = (SessionID) sessionIds.next();
Session.lookupSession(sessionId).logout("user requested");
}
}
public void stop() {
shutdownLatch.countDown();
}
public JFrame getFrame() {
return frame;
}
public static Banzai get() {
return banzai;
}
public static void main(String args[]) throws Exception {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
log.info(e.getMessage(), e);
}
banzai = new Banzai(args);
if (!System.getProperties().containsKey("openfix")) {
banzai.logon();
}
shutdownLatch.await();
}
}
NOTE: The information in your .settings has to match with the information you have in your code.
After you start both codes you will start to receive logon, and heartbeat messages.
I am looking for a way to connect to multiple instances of Glassfish 4+ (JDK7-EE) simultaneously from a stand-alone Swing-based client (JDK7-SE). I successfully connect to a single instance by the following way:
That's the construction of the initial context:
private void connect(String address, String port) {
System.setProperty("java.naming.factory.initial", "com.sun.enterprise.naming.SerialInitContextFactory");
System.setProperty("com.sun.corba.ee.transport.ORBTCPTimeouts", "500:30000:20:"+Integer.MAX_VALUE);
System.setProperty("com.sun.corba.ee.transport.ORBTCPConnectTimeouts", "250:90000:100:"+Integer.MAX_VALUE);
System.setProperty("com.sun.corba.ee.transport.ORBWaitForResponseTimeout", "300000");
System.setProperty("java.security.auth.login.config", new File("login.conf").getAbsolutePath());
System.setProperty("org.omg.CORBA.ORBInitialHost", address);
System.setProperty("org.omg.CORBA.ORBInitialPort", port);
InitialContext context = new InitialContext();
}
Look-ups are done by JNDI using a remote interface:
context.lookup("java:global/LawSuiteEE/LawSuiteEE-ejb/GlobalsFacade!ch.lawsuite.control.GlobalsFacadeRemote");
I am using a custom JDBC realm that resides on the server and works fine. On the client side I pass the following login.conf to the initial context (see code above):
default {
com.sun.enterprise.security.auth.login.ClientPasswordLoginModule required debug=true;
};
Authentication is currently done by ProgrammaticLogin:
private void login(String username, char[] password) {
ProgrammaticLogin plogin = new ProgrammaticLogin();
plogin.login(username, password);
}
All of this is working fine! But during startup of the stand-alone client, I want to simultaneously connect to another EJB located on a different server.
Since ProgrammaticLogin has no direct relation to the initial context, I am not sure how to login to two different Glassfish servers simulteneously with different credentials (e.g. username/password) ? Someone any ideas ?
Further examination of the issue has uncovered, that the initial context can only be set once on a per JVM basis. So as soon as the ORB is set up by using System.setProperty(String, String) and the inital context object is instantiated, the design of the SerialInitContextFactory let's you no more change the selected endpoint(s).
Therefore I decide to connect within different JVMs to the different Glassfish servers. So finally I ended up with a separate project that manages the connections to the application server and communicates by RMI with the main project.
Currently my project consists of two different EE projects to which I want connect simultaneously, namely "LawSuiteEE" and "MgmtCenterEE". Here's the new project that handles the connections:
public static void main(String args[]) {
try {
if(args.length==2) {
if(args[1].equals("LawSuiteEE")) {
ILawSuiteEE stub = (ILawSuiteEE) UnicastRemoteObject.exportObject(new LawSuiteEE(), 0);
Registry registry = LocateRegistry.createRegistry(Integer.parseInt(args[0]));
registry.bind("LawSuiteEE", stub);
} else if(args[1].equals("MgmtCenterEE")) {
ILawSuiteEE stub = (ILawSuiteEE) UnicastRemoteObject.exportObject(new MgmtCenterEE(), 0);
Registry registry = LocateRegistry.createRegistry(Integer.parseInt(args[0]));
registry.bind("MgmtCenterEE", stub);
} else {
throw new NumberFormatException();
}
Logger.getLogger(RemoteContext.class.getName()).log(Level.INFO, "Remote context service is listening on port "+args[0]+" for incoming requests delegating to "+args[1]+".");
System.out.println("SIGNAL[READY]");
} else {
throw new NumberFormatException();
}
} catch (RemoteException ex) {
System.exit(1);
} catch (AlreadyBoundException ex) {
System.exit(2);
} catch(NumberFormatException ex) {
System.exit(3);
}
The interface ILawSuiteEE is used for RMI between this and the main project (the second interface IMgmtCenterEE is quite the same):
public interface ILawSuiteEE extends IConcurrentDatastore {
void connect(String address, String port) throws RemoteException;
void disconnect() throws RemoteException;
boolean login(String username, char[] password) throws RemoteException;
}
The appropriate implementation:
public class LawSuiteEE implements ILawSuiteEE {
private InitialContext context;
private ProgrammaticLogin login;
#Override
public void connect(String address, String port) throws RemoteException {
if(context==null) {
try {
System.setProperty("java.naming.factory.initial", "com.sun.enterprise.naming.SerialInitContextFactory");
System.setProperty("com.sun.corba.ee.transport.ORBTCPTimeouts", "500:30000:20:"+Integer.MAX_VALUE);
System.setProperty("com.sun.corba.ee.transport.ORBTCPConnectTimeouts", "250:90000:100:"+Integer.MAX_VALUE);
System.setProperty("com.sun.corba.ee.transport.ORBWaitForResponseTimeout", "300000");
System.setProperty("java.security.auth.login.config", new File("login.conf").getAbsolutePath());
System.setProperty("org.omg.CORBA.ORBInitialHost", address);
System.setProperty("org.omg.CORBA.ORBInitialPort", Integer.toString(port));
Logger.getLogger(RemoteDatastore.class.getName()).log(Level.INFO, "Try to connect to application server at "+System.getProperty("org.omg.CORBA.ORBInitialHost")+":"+System.getProperty("org.omg.CORBA.ORBInitialPort")+" ...");
context = new InitialContext();
} catch (NamingException ex) {
throw new RemoteException(ex.getMessage());
}
}
}
#Override
public void disconnect() throws RemoteException {
if(context!=null) {
try {
context.close();
Logger.getLogger(LawSuiteEE.class.getName()).log(Level.INFO, "Server context successfully closed.");
} catch (NamingException ex) {
Logger.getLogger(LawSuiteEE.class.getName()).log(Level.SEVERE, "Couldn't close server context.");
} finally {
this.facades.clear();
this.services.clear();
this.context=null;
}
}
}
#Override
public boolean login(String username, char[] password) throws RemoteException {
login = new ProgrammaticLogin();
return login.login(username, password);
}
}
In the main project I'm going to connect with the following:
public class LawSuiteDatastore extends Thread implements ILawSuiteEE {
private int port;
private int trials;
private boolean ready;
private Process process;
private ILawSuiteEE stub;
public LawSuiteDatastore() {
this.setName("K+: Remote-Datastore-Connection");
this.port = RemoteDatastoreService.cport++;
}
#Override
public void run() {
try {
Tools.log(RemoteDatastoreService.class, Level.INFO, "Starting RMI registry on port "+port+" for connecting to LawSuiteEE server instance.");
this.process = Runtime.getRuntime().exec(new String[] {"java", "-jar", Context.getWorkingDirectory()+"/lib/LawSuiteSX.jar", Integer.toString(port), "LawSuiteEE"});
//<editor-fold defaultstate="collapsed" desc="Redirect Error Stream">
new Thread(new Runnable() {
#Override
public void run() {
try{
try(DataInputStream in = new DataInputStream(process.getErrorStream())) {
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String line;
while((line=br.readLine())!=null) {
Tools.log(RemoteDatastoreService.class, Level.SEVERE, line);
}
}
} catch(Exception ex){
Tools.log(MgmtCenterDatastore.class, Level.SEVERE, ex.getMessage());
}
}
}).start();
//</editor-fold>
//<editor-fold defaultstate="collapsed" desc="Redirect Output Stream">
new Thread(new Runnable() {
#Override
public void run() {
try{
try(DataInputStream in = new DataInputStream(process.getInputStream())) {
BufferedReader br = new BufferedReader(new InputStreamReader(in));
String line;
while((line=br.readLine())!=null) {
if(line.contains("SIGNAL[READY]")) { ready=true; }
Tools.log(RemoteDatastoreService.class, Level.INFO, line);
}
}
} catch(Exception ex){
Tools.log(MgmtCenterDatastore.class, Level.SEVERE, ex.getMessage());
}
}
}).start();
//</editor-fold>
// keep thread alive as long process is alive
if(process.waitFor()>0) {
// port was already bound
if(process.exitValue()==2) {
// try it with a different port and start over again
if(trials<3) {
process = null;
port = ++RemoteDatastoreService.cport;
trials++;
if(trials<3) {
start();
}
}
}
}
} catch (IOException ex) {
Tools.log(RemoteDatastoreService.class, Level.SEVERE, ex.getMessage());
} catch (InterruptedException ex) {
Tools.log(RemoteDatastoreService.class, Level.SEVERE, ex.getMessage());
}
}
public boolean isReady() {
return ready;
}
public int getTrials() {
return trials;
}
#Override
public void connect(RemoteDatastore datastore) throws RemoteException {
try {
Tools.log(RemoteDatastoreService.class, Level.INFO, "Locating RMI registry on port "+port+" for connecting to LawSuiteEE server instance.");
Registry registry = LocateRegistry.getRegistry(port);
stub = (ILawSuiteEE)registry.lookup("LawSuiteEE");
stub.connect(datastore);
} catch (NotBoundException ex) {
Logger.getLogger(RemoteDatastoreService.class.getName()).log(Level.SEVERE, null, ex);
}
}
#Override
public void disconnect() throws RemoteException {
if(process!=null && stub!=null) {
stub.disconnect();
process.destroy();
} else {
throw new RemoteException("Remote RMI server is not ready.");
}
}
#Override
public boolean login(String username, char[] password) throws RemoteException {
if(process!=null && stub!=null) {
return stub.login(username, password);
} else {
throw new RemoteException("Remote RMI server is not ready.");
}
}
}
How about using multiple threads, one for each server?
You can create a new thread for each connection you need, set up the InitialContext on each thread and connect with the ProgrammaticLogin with different credentials.
You can create your own "custom" thread by implementing the Runnable interface, and create a constructor for it that receives the credentials and/or InitialContext object.
Simple example:
public class MyThread implements Runnable {
private ProgrammaticLogin plogin;
private string user;
private char[] pass;
public MyThread(String username, char[] password,InitialContext context) {
this.user = username;
this.pass = password;
this.plogin = new ProgrammaticLogin();
//add more code here if needed
}
public void run() {
//insert code here when thread will run
}
}
and invoke it thus:
Runnable thread1 = new MyThread("my user1","my pass1",ContextObject1);
Runnable thread2 = new MyThread("my user2","my pass2",ContextObject2);
new Thread(thread1).start();
new Thread(thread2).start();
Of course this is a very simple example and it might not be suitable for your exact needs, but i think it is a good start for what you need. Since each Context and login credentials will run on a different thread they will have their own separate execution stack and you should not experience any concurrency issues (two threads accessing the same object).
However, you should have a good understanding of concurrency and threads otherwise you might run into different exceptions, that are a bit harder to debug due to using multiple threads.
Tom.
I'm trying to start a JMXConnectorServer for management and debug purposes. But I don't want this service to prevent application from exiting normally when the last non-daemon thread is terminated.
In other words, I want the following program to terminate immediately:
public class Main {
public static void main(final String[] args) throws IOException {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
JMXServiceURL jmxUrl = new JMXServiceURL("rmi", null, 0);
JMXConnectorServer connectorServer =
JMXConnectorServerFactory.newJMXConnectorServer(jmxUrl, null, mbs);
connectorServer.start();
}
}
I play with similar issue and wrote this class:
public final class HardDaemonizer extends Thread {
private final Runnable target;
private final String newThreadName;
public HardDaemonizer(Runnable target, String name, String newThreadName) {
super(name == null ? "Daemonizer" : name);
setDaemon(true);
this.target = target;
this.newThreadName = newThreadName;
}
#Override
public void run() {
try {
List<Thread> tb = getSubThreads();
target.run();
List<Thread> ta = new java.util.ArrayList<>(getSubThreads());
ta.removeAll(tb);
for (Thread thread : ta) {
thread.setName(newThreadName);
}
Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException ex) {
Logger.getLogger(HardDaemonizer.class.getName()).log(Level.SEVERE, null, ex);
}
}
public static Thread daemonize(String daemonizerName, String newThreadName, Runnable target) {
HardDaemonizer daemonizer = new HardDaemonizer(target, daemonizerName, newThreadName);
daemonizer.start();
return daemonizer;
}
private static List<Thread> getSubThreads() {
ThreadGroup group = Thread.currentThread().getThreadGroup().getParent();
Thread[] threads = new Thread[group.activeCount()];
group.enumerate(threads);
return java.util.Arrays.asList(threads);
}
}
You can use it in this way:
HardDaemonizer.daemonize(null, "ConnectorServer", new Runnable(){
#Override
public void run() {
try {
connectorServer.start();
} catch (IOException ex) {
Logger.getLogger(Ralph.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
Be careful - it's tricky!
EDIT
Agh... It's not solution for you. It hard-daemonize connector thread only and this thread will be killed when jvm stops. Additionaly you can customize name of this thread.
Alternatively you can add flag completed and sleep in loop in daemonize method until connector server start up.
SIMPLIFIED
This is simplified daemonizer without tricky thread renaming:
public abstract class Daemonizer<T> extends Thread {
private final T target;
private boolean completed = false;
private Exception cause = null;
public Daemonizer(T target) {
super(Daemonizer.class.getSimpleName());
setDaemon(true);
this.target = target;
}
#Override
public void run() {
try {
act(target);
} catch (Exception ex) {
cause = ex;
}
completed = true;
try {
Thread.sleep(Long.MAX_VALUE);
} catch (InterruptedException ex) {
java.util.logging.Logger.getLogger(Daemonizer.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
}
}
public abstract void act(final T target) throws Exception;
public static void daemonize(Daemonizer daemonizer) throws Exception {
daemonizer.start();
while (!daemonizer.completed) {
Thread.sleep(50);
}
if (daemonizer.cause != null) {
throw daemonizer.cause;
}
}
}
Usage:
Daemonizer.daemonize(new Daemonizer<JMXConnectorServer>(server) {
#Override
public void act(JMXConnectorServer server) throws Exception {
server.start();
}
});
Yeah, you will need to so a connectorServer.stop(); at some point.
Edit:
In reading your comments, it sounds like you should do something like:
connectorServer.start();
try {
// create thread-pool
ExecutorService threadPool = Executors...
// submit jobs to the thread-pool
...
threadPool.shutdown();
// wait for the submitted jobs to finish
threadPool.awaitTermination(Long.MAX_LONG, TimeUnit.SECONDS);
} finally {
connectorServer.stop();
}
#Nicholas' idea of the shutdown hook is a good one. Typically, however, I had my main thread wait on some sort of variable that is set from a shutdown() JMX operation. Something like:
public CountDownLatch shutdownLatch = new CountDownLatch(1);
...
// in main
connectorServer.start();
try {
// do the main-thread stuff
shutdownLatch.await();
} finally {
connectorServer.stop();
}
// in some JMX exposed operation
public void shutdown() {
Main.shutdownLatch.countDown();
}
As an aside, you could use my SimpleJMX package to manage your JMX server for you.
JmxServer jmxServer = new JmxServer(8000);
jmxServer.start();
try {
// register our lookupCache object defined below
jmxServer.register(lookupCache);
jmxServer.register(someOtherObject);
} finally {
jmxServer.stop();
}
From my experience, the JMXConnectorServer is only running in a user thread when you create it explicitly.
If you instead configure RMI access for the platform MBean server via system properties, the implicitly created JMX connector server will run as daemon process and not prevent the JMV shutdown. To do this, your code would shrink to the following
public class Main {
public static void main(final String[] args) throws IOException {
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
}
}
but you'll need to set the following system properties:
-Dcom.sun.management.jmxremote.port=1919
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
You could add a JVM Shutdown Hook to stop the connector server.
===== UPDATE =====
Not sure why your shutdown hook doesn't work. Perhaps you can supply your sample code. Here's an example:
public static void main(String[] args) {
try {
log("Creating Connector Server");
final JMXConnectorServer jcs = JMXConnectorServerFactory.newJMXConnectorServer(new JMXServiceURL("rmi", "localhost", 12387), null, ManagementFactory.getPlatformMBeanServer());
Thread jcsStopper = new Thread("JCS-Stopper") {
public void run() {
if(jcs.isActive()) {
try {
jcs.stop();
log("Connector Server Stopped");
} catch (Exception e) {
log("Failed to stop JCS");
e.printStackTrace();
}
}
}
};
jcsStopper.setDaemon(false);
Runtime.getRuntime().addShutdownHook(jcsStopper);
log("Registered Server Stop Task");
jcs.start();
log("Server Started");
Thread.sleep(3000);
System.exit(0);
} catch (Exception ex) {
ex.printStackTrace(System.err);
}
}
Output is:
[main]:Creating Connector Server
[main]:Registered Server Stop Task
[main]:Server Started
[JCS-Stopper]:Connector Server Stopped
String port = getProperty("com.sun.management.jmxremote.port");
if (port == null) {
port = String.valueOf(getAvailablePort());
System.setProperty("com.sun.management.jmxremote.port", port);
System.setProperty("com.sun.management.jmxremote.ssl", "false");
System.setProperty("com.sun.management.jmxremote.authenticate", "false");
sun.management.Agent.startAgent();
}
log.info(InetAddress.getLocalHost().getCanonicalHostName() + ":" + port);
I have own connection pool:
public final class ConnectionPool {
private static final Logger log = Logger.getLogger(ConnectionPool.class);
private static final int DEFAULT_POOL_SIZE = 10;
//single instance
private static ConnectionPool instance;
//queue of free connections
private BlockingQueue<Connection> connectionQueue;
public ConnectionPool(String driver, String url, String user,
String password, int poolSize)
throws ClassNotFoundException, DAOException{
try{
Class.forName(driver);
connectionQueue = new ArrayBlockingQueue<Connection>(poolSize);
for(int i = 0; i < poolSize ;i++){
Connection connection = DriverManager.getConnection(url, user, password);
connectionQueue.offer(connection);
}
}
catch (SQLException e) {
log.error(e);
throw new DAOException(e.getMessage());
}
}
public static void init() throws DAOException{
try {
if(instance == null){
String driver = ConfigurationManager.
getInstance().getProperty(ConfigurationManager.DATABASE_DRIVER_NAME);
String url = ConfigurationManager.
getInstance().getProperty(ConfigurationManager.DATABASE_URL);
String user = ConfigurationManager.
getInstance().getProperty(ConfigurationManager.DATABASE_USER);
String password = ConfigurationManager.
getInstance().getProperty(ConfigurationManager.DATABASE_PASSWORD);
String poolSizeStr = ConfigurationManager.
getInstance().getProperty(ConfigurationManager.DATABASE_POOLSIZE);
int poolSize = (poolSizeStr != null) ?
Integer.parseInt(poolSizeStr) : DEFAULT_POOL_SIZE;
log.info("Trying to create pool of connections...");
instance = new ConnectionPool(driver,url,user,password,poolSize);
log.info("Connection pool initialized");
}
}catch (ClassNotFoundException e) {
log.error(e);
} catch (SQLException e) {
log.error(e);
throw new DAOException(e.getMessage());
}
}
public static void dispose() throws DAOException {
try {
if(instance != null){
instance.clearConnectionQueue();
instance = null;
log.info("Connection queue is disposed");
}
} catch (DAOException e) {
log.info(e.getMessage());
throw new DAOException(e.getMessage());
}
}
public static ConnectionPool getInstance(){
return instance;
}
public Connection takeConnection() {
Connection connection = null;
try{
connection = connectionQueue.take();
}catch (InterruptedException e) {
log.info("Free connection waiting interrupted.Returned null connection");
log.error(e);
}
return connection;
}
public static void releaseConnection(Connection connection) throws DAOException {
try {
if(!connection.isClosed()){
if(!getInstance().connectionQueue.offer(connection)){
log.info("Connections is not added.");
}
}
else{
log.info("Trying to release closed connection.");
}
} catch (SQLException e) {
log.info("SQLException at connection isClosed(). Connection is not added");
throw new DAOException(e.getMessage());
}
}
private void clearConnectionQueue() throws DAOException{
try {
Connection connection;
while((connection = connectionQueue.poll()) != null){
if(!connection.getAutoCommit()){
connection.commit();
connection.close();
}
}
} catch (SQLException e) {
log.info(e.getMessage());
throw new DAOException(e.getMessage());
}
}
}
And I now I'm initialise and destroy it with listener and load properties using my own class ConfigurationManager,connected with ResourceBundle:
public final class ConfigurationManager {
private static ConfigurationManager instance;
private ResourceBundle resourceBundle;
//getting info from config.properties
private static final String BUNDLE_NAME = "config";
public static final String DATABASE_DRIVER_NAME =
"DATABASE_DRIVER_NAME";
public static final String DATABASE_URL =
"DATABASE_URL";
public static final String DATABASE_USER =
"DATABASE_USER";
public static final String DATABASE_PASSWORD =
"DATABASE_PASSWORD";
public static final String ERROR_PAGE_PATH =
"ERROR_PAGE_PATH";
public static final String BEAN_PATH =
"BEAN_PATH";
public static final String DATABASE_POOLSIZE =
"DATABASE_POOLSIZE";
public synchronized static ConfigurationManager getInstance() {
if (instance == null) {
instance = new ConfigurationManager();
instance.resourceBundle =
ResourceBundle.getBundle(BUNDLE_NAME);
}
return instance;
}
public String getProperty(String key) {
return (String)resourceBundle.getObject(key);
}
}
But I want to do it(init,destroy,properties), using Spring. So how could I do it?
Use Spring's init and destroy methods, of course:
http://www.mkyong.com/spring/spring-init-method-and-destroy-method-example/
Personally, I think this is ill-advised. You aren't likely to improve on the pools already available to you (e.g. C3P0). You may do worse. But that's your choice.