1/*
2 * Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.jmx.remote.internal;
27
28import java.io.IOException;
29import java.io.InterruptedIOException;
30
31import com.sun.jmx.remote.util.ClassLogger;
32import com.sun.jmx.remote.util.EnvHelp;
33
34public abstract class ClientCommunicatorAdmin {
35    private static volatile long threadNo = 1;
36
37    public ClientCommunicatorAdmin(long period) {
38        this.period = period;
39
40        if (period > 0) {
41            checker = new Checker();
42
43            Thread t = new Thread(null,
44                                  checker,
45                                  "JMX client heartbeat " +  (++threadNo),
46                                  0,
47                                  false);
48
49            t.setDaemon(true);
50            t.start();
51        } else
52            checker = null;
53    }
54
55    /**
56     * Called by a client to inform of getting an IOException.
57     */
58    public void gotIOException (IOException ioe) throws IOException {
59        restart(ioe);
60    }
61
62    /**
63     * Called by this class to check a client connection.
64     */
65    protected abstract void checkConnection() throws IOException;
66
67    /**
68     * Tells a client to re-start again.
69     */
70    protected abstract void doStart() throws IOException;
71
72    /**
73     * Tells a client to stop because failing to call checkConnection.
74     */
75    protected abstract void doStop();
76
77    /**
78     * Terminates this object.
79     */
80    public void terminate() {
81        synchronized(lock) {
82            if (state == TERMINATED) {
83                return;
84            }
85
86            state = TERMINATED;
87
88            lock.notifyAll();
89
90            if (checker != null)
91                checker.stop();
92        }
93    }
94
95    private void restart(IOException ioe) throws IOException {
96        // check state
97        synchronized(lock) {
98            if (state == TERMINATED) {
99                throw new IOException("The client has been closed.");
100            } else if (state == FAILED) { // already failed to re-start by another thread
101                throw ioe;
102            } else if (state == RE_CONNECTING) {
103                // restart process has been called by another thread
104                // we need to wait
105                while(state == RE_CONNECTING) {
106                    try {
107                        lock.wait();
108                    } catch (InterruptedException ire) {
109                        // be asked to give up
110                        InterruptedIOException iioe = new InterruptedIOException(ire.toString());
111                        EnvHelp.initCause(iioe, ire);
112
113                        throw iioe;
114                    }
115                }
116
117                if (state == TERMINATED) {
118                    throw new IOException("The client has been closed.");
119                } else if (state != CONNECTED) {
120                    // restarted is failed by another thread
121                    throw ioe;
122                }
123                return;
124            } else {
125                state = RE_CONNECTING;
126                lock.notifyAll();
127            }
128        }
129
130        // re-starting
131        try {
132            doStart();
133            synchronized(lock) {
134                if (state == TERMINATED) {
135                    throw new IOException("The client has been closed.");
136                }
137
138                state = CONNECTED;
139
140                lock.notifyAll();
141            }
142
143            return;
144        } catch (Exception e) {
145            logger.warning("restart", "Failed to restart: " + e);
146            logger.debug("restart",e);
147
148            synchronized(lock) {
149                if (state == TERMINATED) {
150                    throw new IOException("The client has been closed.");
151                }
152
153                state = FAILED;
154
155                lock.notifyAll();
156            }
157
158            try {
159                doStop();
160            } catch (Exception eee) {
161                // OK.
162                // We know there is a problem.
163            }
164
165            terminate();
166
167            throw ioe;
168        }
169    }
170
171// --------------------------------------------------------------
172// private varaibles
173// --------------------------------------------------------------
174    private class Checker implements Runnable {
175        public void run() {
176            myThread = Thread.currentThread();
177
178            while (state != TERMINATED && !myThread.isInterrupted()) {
179                try {
180                    Thread.sleep(period);
181                } catch (InterruptedException ire) {
182                    // OK.
183                    // We will check the state at the following steps
184                }
185
186                if (state == TERMINATED || myThread.isInterrupted()) {
187                    break;
188                }
189
190                try {
191                    checkConnection();
192                } catch (Exception e) {
193                    synchronized(lock) {
194                        if (state == TERMINATED || myThread.isInterrupted()) {
195                            break;
196                        }
197                    }
198
199                    e = (Exception)EnvHelp.getCause(e);
200
201                    if (e instanceof IOException &&
202                        !(e instanceof InterruptedIOException)) {
203                        try {
204                            gotIOException((IOException)e);
205                        } catch (Exception ee) {
206                            logger.warning("Checker-run",
207                                           "Failed to check connection: "+ e);
208                            logger.warning("Checker-run", "stopping");
209                            logger.debug("Checker-run",e);
210
211                            break;
212                        }
213                    } else {
214                        logger.warning("Checker-run",
215                                     "Failed to check the connection: " + e);
216                        logger.debug("Checker-run",e);
217
218                        // XXX stop checking?
219
220                        break;
221                    }
222                }
223            }
224
225            if (logger.traceOn()) {
226                logger.trace("Checker-run", "Finished.");
227            }
228        }
229
230        private void stop() {
231            if (myThread != null && myThread != Thread.currentThread()) {
232                myThread.interrupt();
233            }
234        }
235
236        private Thread myThread;
237    }
238
239// --------------------------------------------------------------
240// private variables
241// --------------------------------------------------------------
242    private final Checker checker;
243    private long period;
244
245    // state
246    private final static int CONNECTED = 0;
247    private final static int RE_CONNECTING = 1;
248    private final static int FAILED = 2;
249    private final static int TERMINATED = 3;
250
251    private int state = CONNECTED;
252
253    private final int[] lock = new int[0];
254
255    private static final ClassLogger logger =
256        new ClassLogger("javax.management.remote.misc",
257                        "ClientCommunicatorAdmin");
258}
259