/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2006-2011 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ package com.sun.enterprise.admin.jmx.remote.server.servlet; import com.sun.enterprise.module.ModulesRegistry; import com.sun.enterprise.module.common_impl.LogHelper; import com.sun.enterprise.util.LocalStringManagerImpl; import com.sun.enterprise.v3.common.ActionReporter; import com.sun.enterprise.v3.common.HTMLActionReporter; import com.sun.enterprise.v3.common.PropsFileActionReporter; import com.sun.logging.LogDomains; import org.glassfish.grizzly.http.server.Request; import org.glassfish.grizzly.http.server.Response; import org.glassfish.grizzly.tcp.http11.InternalOutputBuffer; import org.apache.commons.beanutils.ConvertUtils; import org.glassfish.api.ActionReport; import org.glassfish.api.Async; import org.glassfish.api.I18n; import org.glassfish.api.Param; import org.glassfish.api.admin.AdminCommand; import org.glassfish.api.admin.AdminCommandContext; import org.glassfish.api.container.Adapter; import org.glassfish.hk2.api.MultiException; import org.glassfish.hk2.api.ServiceLocator; import org.jvnet.hk2.annotations.Service; import org.jvnet.hk2.component.InjectionManager; import org.jvnet.hk2.component.UnsatisfiedDepedencyException; import java.io.ByteArrayOutputStream; import java.io.UnsupportedEncodingException; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.URLDecoder; import java.util.Properties; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; import javax.inject.Inject; /** * Service the JMX HTTP clients talking to GF v3 using JMX Service URL... * @author ne110415 */ @Service public class JMXHTTPAdapter implements Adapter { public final static String PREFIX_URI="/web1/remotejmx"; public final static Logger logger = LogDomains.getLogger(ServerEnvironmentImpl.class, LogDomains.ADMIN_LOGGER); public final static LocalStringManagerImpl adminStrings = new LocalStringManagerImpl(JMXHTTPAdapter.class); @Inject ServiceLocator habitat; @Inject ModulesRegistry modulesRegistry; /** * Call the service method, and notify all listeners * * @exception Exception if an error happens during handling of * the request. Common errors are: * <ul><li>IOException if an input/output error occurs and we are * processing an included servlet (otherwise it is swallowed and * handled by the top level error handler mechanism) * <li>ServletException if a servlet throws an exception and * we are processing an included servlet (otherwise it is swallowed * and handled by the top level error handler mechanism) * </ul> * Tomcat should be able to handle and log any other exception ( including * runtime exceptions ) */ public void service(Request req, Response res) throws Exception { LogHelper.getDefaultLogger().info("New HTTP JMX adapter !"); LogHelper.getDefaultLogger().info("Received something on " + req.requestURI()); LogHelper.getDefaultLogger().info("QueryString = " + req.queryString()); // so far, I only use HTMLActionReporter, but I should really look at // the request client. ActionReporter report; if (req.getHeader("User-Agent").startsWith("hk2")) { report = new PropsFileActionReporter(); } else { report = new HTMLActionReporter(); } doCommand(req, report); InternalOutputBuffer outputBuffer = (InternalOutputBuffer) res.getOutputBuffer(); res.setStatus(200); res.setContentType("text/html"); ByteArrayOutputStream bos = new ByteArrayOutputStream(); report.writeReport(bos); res.setContentLength(bos.size()); outputBuffer.flush(); outputBuffer.realWriteBytes(bos.toByteArray(), 0, bos.size()); res.finish(); } private void doCommand(Request req, ActionReport report) { String requestURI = req.requestURI().toString(); if (!requestURI.startsWith(PREFIX_URI)) { String msg = adminStrings.getLocalString("adapter.panic", "Wrong request landed in AdminAdapter {0}", requestURI); report.setMessage(msg); LogHelper.getDefaultLogger().info(msg); return; } String command = requestURI.substring(PREFIX_URI.length()); // extract parameters... final Properties parameters = new Properties(); String queryString = req.queryString().toString(); StringTokenizer stoken = new StringTokenizer(queryString, "?"); while (stoken.hasMoreTokens()) { String token = stoken.nextToken(); if (token.indexOf("=")==-1) continue; String paramName = token.substring(0, token.lastIndexOf("=")); String value = token.substring(token.lastIndexOf("=")+1); try { value = URLDecoder.decode(value, "UTF-8"); } catch (UnsupportedEncodingException e) { logger.log(Level.WARNING, adminStrings.getLocalString("adapter.param.decode", "Cannot decode parameter {0} = {1}")); } parameters.setProperty(paramName, value); } // Dump parameters... if (logger.isLoggable(Level.FINER)) { for (Object key : parameters.keySet()) { logger.finer("Key " + key + " = " + parameters.getProperty((String) key)); } } doCommand(command, parameters, report); } public void doCommand(final String commandName, final Properties parameters, final ActionReport report) { final AdminCommand handler = getCommand(commandName, report, logger); if (handler==null) { return; } if (parameters.size()==1 && parameters.get("help")!=null) { usage(commandName, handler, report); return; } report.setActionDescription(commandName + " AdminCommand"); final AdminCommandContext context = new AdminCommandContext( LogDomains.getLogger(ServerEnvironmentImpl.class, LogDomains.ADMIN_LOGGER), report, parameters); // initialize the injector. InjectionManager injectionMgr = new InjectionManager<Param>() { protected boolean isOptional(Param annotation) { return annotation.optional(); } protected Object getValue(Object component, AnnotatedElement target, Class type) throws MultiException { // look for the name in the list of parameters passed. Param param = target.getAnnotation(Param.class); if (param.primary()) { // this is the primary parameter for the command String value = parameters.getProperty("DEFAULT"); if (value!=null) { return value; } } return ConvertUtils.convert((String) parameters.get(getParamName(param, target)), type); } }; LocalStringManagerImpl localStrings = new LocalStringManagerImpl(handler.getClass()); // Let's get the command i18n key I18n i18n = handler.getClass().getAnnotation(I18n.class); String i18n_key = ""; if (i18n!=null) { i18n_key = i18n.value(); } // inject try { injectionMgr.inject(handler, Param.class); } catch (UnsatisfiedDepedencyException e) { Param param = e.getUnsatisfiedElement().getAnnotation(Param.class); String paramName = getParamName(param, e.getUnsatisfiedElement()); String paramDesc = getParamDescription(localStrings, i18n_key, paramName, e.getUnsatisfiedElement()); String errorMsg; if (paramDesc!=null) { errorMsg = adminStrings.getLocalString("admin.param.missing", "{0} command requires the {1} parameter : {2}", commandName, paramName, paramDesc); } else { errorMsg = adminStrings.getLocalString("admin.param.missing.nodesc", "{0} command requires the {1} parameter", commandName, paramName); } logger.severe(errorMsg); report.setActionExitCode(ActionReport.ExitCode.FAILURE); report.setMessage(errorMsg); report.setFailureCause(e); return; } catch (MultiException e) { logger.severe(e.getMessage()); report.setActionExitCode(ActionReport.ExitCode.FAILURE); report.setMessage(e.getMessage()); report.setFailureCause(e); } // the command may be an asynchronous command, so we need to check // for the @Async annotation. Async async = handler.getClass().getAnnotation(Async.class); if (async==null) { try { handler.execute(context); } catch(Throwable e) { logger.log(Level.SEVERE, adminStrings.getLocalString("adapter.exception","Exception in command execution : ", e), e); } } else { Thread t = new Thread() { public void run() { try { handler.execute(context); } catch (RuntimeException e) { logger.log(Level.SEVERE,e.getMessage(), e); } } }; t.setPriority(async.priority()); t.start(); report.setActionExitCode(ActionReport.ExitCode.SUCCESS); report.setMessage( adminStrings.getLocalString("adapter.command.launch", "{0} launch successful", commandName)); } } public void usage(String commandName, AdminCommand command, ActionReport report) { report.setActionDescription(commandName + " help"); LocalStringManagerImpl localStrings = new LocalStringManagerImpl(command.getClass()); // Let's get the command i18n key I18n i18n = command.getClass().getAnnotation(I18n.class); String i18nKey = ""; if (i18n!=null) { i18nKey = i18n.value(); } report.setMessage(localStrings.getLocalString(i18nKey, null)); for (Field f : command.getClass().getDeclaredFields()) { addParamUsage(report, localStrings, i18nKey, f); } for (Method m : command.getClass().getDeclaredMethods()) { addParamUsage(report, localStrings, i18nKey, m); } report.setActionExitCode(ActionReport.ExitCode.SUCCESS); } private void addParamUsage(ActionReport report, LocalStringManagerImpl localStrings, String i18nKey, AnnotatedElement annotated) { Param param = annotated.getAnnotation(Param.class); if (param!=null) { // this is a param. String paramName = getParamName(param, annotated); report.getTopMessagePart().addProperty(paramName, getParamDescription(localStrings, i18nKey, paramName, annotated)); } } private String getParamDescription(LocalStringManagerImpl localStrings, String i18nKey, String paramName, AnnotatedElement annotated) { I18n i18n = annotated.getAnnotation(I18n.class); String paramDesc; if (i18n==null) { paramDesc = localStrings.getLocalString(i18nKey+"."+paramName, null); } else { paramDesc = localStrings.getLocalString(i18n.value(), null); } if (paramDesc==null) { paramDesc = adminStrings.getLocalString("adapter.nodesc", "no description provided"); } return paramDesc; } private String getParamName(Param param, AnnotatedElement annotated) { if (param.name().equals("")) { if (annotated instanceof Field) { return ((Field) annotated).getName(); } if (annotated instanceof Method) { return ((Method) annotated).getName().substring(3).toLowerCase(); } } else { return param.name(); } return ""; } /** * Finish the response and recycle the request/response tokens. Base on * the connection header, the underlying socket transport will be closed */ public void afterService(Request req, Response res) throws Exception { } /** * Notify all container event listeners that a particular event has * occurred for this Adapter. The default implementation performs * this notification synchronously using the calling thread. * * @param type Event type * @param data Event data */ public void fireAdapterEvent(String type, Object data) { } /** * Returns the context root for this adapter * * @return context root */ public String getContextRoot() { return PREFIX_URI; } /** * Return Command handlers from the lookup or if not found in the lookup, * look at META-INF/services implementations and add them to the lookup * @param commandName the request handler's command name * @param report the reporting facility * @return the admin command handler if found * */ private AdminCommand getCommand(String commandName, ActionReport report, Logger logger) { AdminCommand command = null; try { command = habitat.getService(AdminCommand.class, commandName); } catch(MultiException e) { } if (command==null) { String msg = adminStrings.getLocalString("adapter.command.notfound", "Command {0} not found", commandName); report.setMessage(msg); LogHelper.getDefaultLogger().info(msg); } return command; } }