001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *  http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    
020    package org.apache.geronimo.commands;
021    
022    import java.util.HashMap;
023    import java.util.Map;
024    import java.util.Set;
025    import java.util.Iterator;
026    import java.io.IOException;
027    
028    import javax.management.remote.JMXServiceURL;
029    import javax.management.remote.JMXConnector;
030    import javax.management.remote.JMXConnectorFactory;
031    import javax.management.remote.rmi.RMIConnectorServer;
032    import javax.management.MBeanServerConnection;
033    import javax.management.ObjectName;
034    import javax.rmi.ssl.SslRMIClientSocketFactory;
035    
036    //
037    // FIXME: It should be possible to query state with-out any Geronimo classes,
038    //        just using JMX interfaces.
039    //
040    
041    import org.apache.geronimo.gbean.AbstractName;
042    import org.apache.geronimo.gbean.AbstractNameQuery;
043    import org.apache.geronimo.kernel.Kernel;
044    import org.apache.geronimo.kernel.config.PersistentConfigurationList;
045    
046    import org.slf4j.Logger;
047    import org.slf4j.LoggerFactory;
048    
049    /**
050     * Helper to communicate with a remote server via JMX.
051     *
052     * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
053     */
054    public class ServerProxy
055    {
056        private static final Logger log = LoggerFactory.getLogger(ServerProxy.class);
057    
058        private JMXServiceURL url;
059        
060        private JMXConnector connector;
061    
062        private Map environment;
063    
064        private MBeanServerConnection mbeanConnection;
065    
066        private Throwable lastError;
067    
068        public ServerProxy(final JMXServiceURL url, final Map environment) throws Exception {
069            assert url != null;
070            assert environment != null;
071    
072            this.url = url;
073            this.environment = environment;
074            
075            log.debug("Initialized with URL: " + url + ", environment: " + environment);
076        }
077    
078        public ServerProxy(String hostname, int port, String username, String password) throws Exception {
079            this(hostname, port, username, password, false);
080        }
081        
082        public ServerProxy(String hostname, int port, String username, String password, boolean secure) throws Exception {
083            this(createJMXServiceURL(hostname, port, secure), username, password, secure);
084        }
085    
086        public ServerProxy(String url, String username, String password) throws Exception {
087            this(url, username, password, false);
088        }
089    
090        public ServerProxy(String url, String username, String password, boolean secure) throws Exception {
091            assert url != null;
092            assert username != null;
093            assert password != null;
094            
095            this.url = new JMXServiceURL(url);
096            this.environment = new HashMap();
097            this.environment.put(JMXConnector.CREDENTIALS, new String[] {username, password});
098            if (secure) {
099                SslRMIClientSocketFactory csf = new SslRMIClientSocketFactory();
100                this.environment.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf);
101            }
102    
103            log.debug("Initialized with URL: " + url + ", environment: " + environment);
104        }
105    
106        public ServerProxy(JMXConnector connector) throws Exception {
107            this.mbeanConnection = connector.getMBeanServerConnection();
108        }
109        
110        private static String createJMXServiceURL(String hostname, int port, boolean secure) {
111            String connectorName = (secure) ? "/JMXSecureConnector" : "/JMXConnector";
112            return "service:jmx:rmi://" + hostname + "/jndi/rmi://" + hostname + ":" + port + connectorName;
113        }
114        
115        private MBeanServerConnection getConnection() throws IOException {
116            if (this.mbeanConnection == null) {
117                log.debug("Connecting to: " + url);
118                
119                connector = JMXConnectorFactory.connect(url, environment);
120                this.mbeanConnection = connector.getMBeanServerConnection();
121                
122                log.debug("Connected");
123            }
124    
125            return mbeanConnection;
126        }
127        
128        public void closeConnection() {
129            if (connector != null) {
130                    try {
131                            connector.close();
132                    } catch (IOException e) {
133                            String msg = "Failed to close JMXConnector";
134                            if (log.isTraceEnabled()) {
135                                    log.trace(msg,e);
136                            }
137                            if (log.isDebugEnabled()) {
138                                    log.debug(msg + ":" + e);
139                            }
140                    }
141            }
142        }
143    
144        public boolean isFullyStarted() {
145            boolean fullyStarted = true;
146    
147            try {
148                AbstractNameQuery query = new AbstractNameQuery(PersistentConfigurationList.class.getName());
149                Set result = listGBeans(query);
150                Iterator iter = result.iterator();
151                while (iter.hasNext()) {
152                    AbstractName name = (AbstractName)iter.next();
153                    boolean started = getBooleanAttribute(name, "kernelFullyStarted");
154                    if (!started) {
155                        fullyStarted = false;
156                        break;
157                    }
158                }
159            }
160            catch (IOException e) {
161                String msg = "Connection failure; ignoring";
162                if (log.isTraceEnabled()) {
163                    log.trace(msg, e);
164                }
165                else if (log.isDebugEnabled()) {
166                    log.debug(msg + ": " + e);
167                }
168                
169                fullyStarted = false;
170                lastError = e;
171            }
172            catch (Exception e) {
173                String msg = "Unable to determine if the server is fully started; ignoring";
174                if (log.isTraceEnabled()) {
175                    log.trace(msg, e);
176                }
177                else if (log.isDebugEnabled()) {
178                    log.debug(msg + ": " + e);
179                }
180                
181                fullyStarted = false;
182                lastError = e;
183            }
184            
185            return fullyStarted;
186        }
187    
188        public String getGeronimoHome() {
189            String home = null;
190    
191            try {
192                ObjectName systemInfoQuery = new ObjectName("*:name=ServerInfo,j2eeType=GBean,*");
193    
194                getConnection();
195    
196                Set set = mbeanConnection.queryNames(systemInfoQuery, null);
197    
198                if (set.size() > 0) {
199                    ObjectName found = (ObjectName)set.iterator().next();
200                    home = (String)mbeanConnection.getAttribute(found, "currentBaseDirectory");
201                }
202            }
203            catch (IOException e) {
204                String msg = "Connection failure; ignoring";
205                if (log.isTraceEnabled()) {
206                    log.trace(msg, e);
207                }
208                else if (log.isDebugEnabled()) {
209                    log.debug(msg + ": " + e);
210                }
211                
212                lastError = e;
213            }
214            catch (Exception e) {
215                String msg = "Unable to determine if the server is fully started; ignoring";
216                if (log.isTraceEnabled()) {
217                    log.trace(msg, e);
218                }
219                else if (log.isDebugEnabled()) {
220                    log.debug(msg + ": " + e);
221                }
222                
223                lastError = e;
224            }
225            
226            return home;
227        }
228    
229        public Throwable getLastError() {
230            return lastError;
231        }
232    
233        public void shutdown() throws Exception {
234            invoke("shutdown");
235        }
236    
237        //
238        // Kernel invocation helpers
239        //
240    
241        private Object invoke(final String operation, final Object[] args, final String[] signature) throws Exception {
242            assert operation != null;
243            assert args != null;
244            assert signature != null;
245    
246            return getConnection().invoke(Kernel.KERNEL, operation, args, signature);
247        }
248    
249        private Object invoke(final String operation, final Object[] args) throws Exception {
250            assert args != null;
251    
252            String[] signature = new String[args.length];
253            for (int i=0; i<args.length; i++) {
254                signature[i] = args[i].getClass().getName();
255            }
256    
257            return invoke(operation, args, signature);
258        }
259    
260        private Object invoke(final String operation) throws Exception {
261            return invoke(operation, new Object[0]);
262        }
263    
264        private Set listGBeans(final AbstractNameQuery query) throws Exception {
265            return (Set)invoke("listGBeans", new Object[] { query });
266        }
267    
268        private Object getAttribute(final AbstractName name, final String attribute) throws Exception {
269            assert name != null;
270            assert attribute != null;
271    
272            return invoke("getAttribute", new Object[] { name, attribute });
273        }
274    
275        private boolean getBooleanAttribute(final AbstractName name, final String attribute) throws Exception {
276            Object obj = getAttribute(name, attribute);
277            if (obj instanceof Boolean) {
278                return ((Boolean)obj).booleanValue();
279            }
280            else {
281                throw new RuntimeException("Attribute is not of type Boolean: " + attribute);
282            }
283        }
284    }