1/*
2 * Copyright (c) 2004, 2014, 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 sun.jvmstat.perfdata.monitor.protocol.rmi;
27
28import sun.jvmstat.monitor.*;
29import sun.jvmstat.monitor.event.*;
30import sun.jvmstat.monitor.remote.*;
31import sun.jvmstat.perfdata.monitor.*;
32import java.util.*;
33import java.net.*;
34import java.io.*;
35import java.rmi.*;
36import java.util.HashMap;
37
38/**
39 * Concrete implementation of the MonitoredHost interface for the
40 * <em>rmi</em> protocol of the HotSpot PerfData monitoring implementation.
41 *
42 * @author Brian Doherty
43 * @since 1.5
44 */
45public class MonitoredHostProvider extends MonitoredHost {
46    private static final String serverName = "/JStatRemoteHost";
47    private static final int DEFAULT_POLLING_INTERVAL = 1000;
48
49    private ArrayList<HostListener> listeners;
50    private NotifierTask task;
51    private HashSet<Integer> activeVms;
52    private RemoteVmManager vmManager;
53    private RemoteHost remoteHost;
54    private Timer timer;
55
56    /**
57     * Create a MonitoredHostProvider instance using the given HostIdentifier.
58     *
59     * @param hostId the host identifier for this MonitoredHost
60     * @throws MonitorException Thrown on any error encountered while
61     *                          communicating with the remote host.
62     */
63    public MonitoredHostProvider(HostIdentifier hostId)
64           throws MonitorException {
65        this.hostId = hostId;
66        this.listeners = new ArrayList<HostListener>();
67        this.interval = DEFAULT_POLLING_INTERVAL;
68        this.activeVms = new HashSet<Integer>();
69
70        String rmiName;
71        String sn = serverName;
72        String path = hostId.getPath();
73
74        if ((path != null) && (path.length() > 0)) {
75            sn = path;
76        }
77
78        if (hostId.getPort() != -1) {
79            rmiName = "rmi://" + hostId.getHost() + ":" + hostId.getPort() + sn;
80        } else {
81            rmiName = "rmi://" + hostId.getHost() + sn;
82        }
83
84        try {
85            remoteHost = (RemoteHost)Naming.lookup(rmiName);
86
87        } catch (RemoteException e) {
88            /*
89             * rmi registry not available
90             *
91             * Access control exceptions, where the rmi server refuses a
92             * connection based on policy file configuration, come through
93             * here on the client side. Unfortunately, the RemoteException
94             * doesn't contain enough information to determine the true cause
95             * of the exception. So, we have to output a rather generic message.
96             */
97            String message = "RMI Registry not available at "
98                             + hostId.getHost();
99
100            if (hostId.getPort() == -1) {
101                message = message + ":"
102                          + java.rmi.registry.Registry.REGISTRY_PORT;
103            } else {
104                message = message + ":" + hostId.getPort();
105            }
106
107            if (e.getMessage() != null) {
108                throw new MonitorException(message + "\n" + e.getMessage(), e);
109            } else {
110                throw new MonitorException(message, e);
111            }
112
113        } catch (NotBoundException e) {
114            // no server with given name
115            String message = e.getMessage();
116            if (message == null) message = rmiName;
117            throw new MonitorException("RMI Server " + message
118                                       + " not available", e);
119        } catch (MalformedURLException e) {
120            // this is a programming problem
121            e.printStackTrace();
122            throw new IllegalArgumentException("Malformed URL: " + rmiName);
123        }
124        this.vmManager = new RemoteVmManager(remoteHost);
125        this.timer = new Timer(true);
126    }
127
128    /**
129     * {@inheritDoc}
130     */
131    public MonitoredVm getMonitoredVm(VmIdentifier vmid)
132                       throws MonitorException {
133        return getMonitoredVm(vmid, DEFAULT_POLLING_INTERVAL);
134    }
135
136    /**
137     * {@inheritDoc}
138     */
139    public MonitoredVm getMonitoredVm(VmIdentifier vmid, int interval)
140                       throws MonitorException {
141        VmIdentifier nvmid = null;
142        try {
143            nvmid = hostId.resolve(vmid);
144            RemoteVm rvm = remoteHost.attachVm(vmid.getLocalVmId(),
145                                               vmid.getMode());
146            RemoteMonitoredVm rmvm = new RemoteMonitoredVm(rvm, nvmid, timer,
147                                                           interval);
148            rmvm.attach();
149            return rmvm;
150
151        } catch (RemoteException e) {
152            throw new MonitorException("Remote Exception attaching to "
153                                       + nvmid.toString(), e);
154        } catch (URISyntaxException e) {
155            /*
156             * the VmIdentifier is expected to be a valid and should resolve
157             * easonably against the host identifier. A URISyntaxException
158             * here is most likely a programming error.
159             */
160            throw new IllegalArgumentException("Malformed URI: "
161                                               + vmid.toString(), e);
162        }
163    }
164
165    /**
166     * {@inheritDoc}
167     */
168    public void detach(MonitoredVm vm) throws MonitorException {
169        RemoteMonitoredVm rmvm = (RemoteMonitoredVm)vm;
170        rmvm.detach();
171        try {
172            remoteHost.detachVm(rmvm.getRemoteVm());
173
174        } catch (RemoteException e) {
175            throw new MonitorException("Remote Exception detaching from "
176                                       + vm.getVmIdentifier().toString(), e);
177        }
178    }
179
180    /**
181     * {@inheritDoc}
182     */
183    public void addHostListener(HostListener listener) {
184        synchronized(listeners) {
185            listeners.add(listener);
186            if (task == null) {
187                task = new NotifierTask();
188                timer.schedule(task, 0, interval);
189            }
190        }
191    }
192
193    /**
194     * {@inheritDoc}
195     */
196    public void removeHostListener(HostListener listener) {
197        /*
198         * XXX: if a disconnect method is added, make sure it calls
199         * this method to unregister this object from the watcher. otherwise,
200         * an unused MonitoredHostProvider instance may go uncollected.
201         */
202        synchronized(listeners) {
203            listeners.remove(listener);
204            if (listeners.isEmpty() && (task != null)) {
205                task.cancel();
206                task = null;
207            }
208        }
209    }
210
211    public void setInterval(int newInterval) {
212        synchronized(listeners) {
213            if (newInterval == interval) {
214                return;
215            }
216
217            int oldInterval = interval;
218            super.setInterval(newInterval);
219
220            if (task != null) {
221                task.cancel();
222                NotifierTask oldTask = task;
223                task = new NotifierTask();
224                CountedTimerTaskUtils.reschedule(timer, oldTask, task,
225                                                 oldInterval, newInterval);
226            }
227        }
228    }
229
230    /**
231     * {@inheritDoc}
232     */
233    public Set<Integer> activeVms() throws MonitorException {
234        return vmManager.activeVms();
235    }
236
237    /**
238     * Fire VmStatusChangeEvent events to HostListener objects
239     *
240     * @param active Set of Integer objects containing the local
241     *               Vm Identifiers of the active JVMs
242     * @param started Set of Integer objects containing the local
243     *                Vm Identifiers of new JVMs started since last
244     *                interval.
245     * @param terminated Set of Integer objects containing the local
246     *                   Vm Identifiers of terminated JVMs since last
247     *                   interval.
248     */
249    @SuppressWarnings("unchecked") // Cast of result of clone
250    private void fireVmStatusChangedEvents(Set<Integer> active, Set<Integer> started,
251                                           Set<Integer> terminated) {
252        ArrayList<HostListener> registered = null;
253        VmStatusChangeEvent ev = null;
254
255        synchronized(listeners) {
256            registered = (ArrayList)listeners.clone();
257        }
258
259        for (Iterator<HostListener> i = registered.iterator(); i.hasNext(); /* empty */) {
260            HostListener l = i.next();
261            if (ev == null) {
262                ev = new VmStatusChangeEvent(this, active, started, terminated);
263            }
264            l.vmStatusChanged(ev);
265        }
266    }
267
268    /**
269     * Fire hostDisconnectEvent events.
270     */
271    @SuppressWarnings("unchecked") // Cast of result of clone
272    void fireDisconnectedEvents() {
273        ArrayList<HostListener> registered = null;
274        HostEvent ev = null;
275
276        synchronized(listeners) {
277            registered = (ArrayList)listeners.clone();
278        }
279
280        for (Iterator<HostListener> i = registered.iterator(); i.hasNext(); /* empty */) {
281            HostListener l = i.next();
282            if (ev == null) {
283                ev = new HostEvent(this);
284            }
285            l.disconnected(ev);
286        }
287    }
288
289    /**
290     * class to poll the remote machine and generate local event notifications.
291     */
292    private class NotifierTask extends CountedTimerTask {
293        public void run() {
294            super.run();
295
296            // save the last set of active JVMs
297            Set<Integer> lastActiveVms = activeVms;
298
299            try {
300                // get the current set of active JVMs
301                activeVms = (HashSet<Integer>)vmManager.activeVms();
302
303            } catch (MonitorException e) {
304                // XXX: use logging api
305                System.err.println("MonitoredHostProvider: polling task "
306                                   + "caught MonitorException:");
307                e.printStackTrace();
308
309                // mark the HostManager as errored and notify listeners
310                setLastException(e);
311                fireDisconnectedEvents();
312            }
313
314            if (activeVms.isEmpty()) {
315                return;
316            }
317
318            Set<Integer> startedVms = new HashSet<>();
319            Set<Integer> terminatedVms = new HashSet<>();
320
321            for (Iterator<Integer> i = activeVms.iterator(); i.hasNext(); /* empty */ ) {
322                Integer vmid = i.next();
323                if (!lastActiveVms.contains(vmid)) {
324                    // a new file has been detected, add to set
325                    startedVms.add(vmid);
326                }
327            }
328
329            for (Iterator<Integer> i = lastActiveVms.iterator(); i.hasNext();
330                    /* empty */ ) {
331                Integer o = i.next();
332                if (!activeVms.contains(o)) {
333                    // JVM has terminated, remove it from the active list
334                    terminatedVms.add(o);
335                }
336            }
337
338            if (!startedVms.isEmpty() || !terminatedVms.isEmpty()) {
339                fireVmStatusChangedEvents(activeVms, startedVms, terminatedVms);
340            }
341        }
342    }
343}
344