package org.jolokia.request; import java.util.*; import javax.management.MalformedObjectNameException; import org.jolokia.config.ProcessingParameters; import org.jolokia.util.EscapeUtil; import org.jolokia.util.RequestType; /* * Copyright 2009-2013 Roland Huss * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Factory for creating {@link JmxRequest}s * * @author roland * @since Oct 29, 2009 */ public final class JmxRequestFactory { // private constructor for static class private JmxRequestFactory() { } /** * * Create a JMX request from a GET Request with a REST Url. * <p> * The REST-Url which gets recognized has the following format: * <p> * <pre> * <base_url>/<type>/<param1>/<param2>/.... * </pre> * <p> * where <code>base_url<code> is the URL specifying the overall servlet (including * the servlet context, something like "http://localhost:8080/j4p-agent"), * <code>type</code> the operational mode and <code>param1 .. paramN<code> * the provided parameters which are dependend on the <code>type<code> * <p> * The following types are recognized so far, along with there parameters: * * <ul> * <li>Type: <b>read</b> ({@link RequestType#READ}<br/> * Parameters: <code>param1<code> = MBean name, <code>param2</code> = Attribute name, * <code>param3 ... paramN</code> = Inner Path. * The inner path is optional and specifies a path into complex MBean attributes * like collections or maps. If within collections/arrays/tabular data, * <code>paramX</code> should specify * a numeric index, in maps/composite data <code>paramX</code> is a used as a string * key.</li>. If the attribute name contains "," it is interpreted as a list of attributes. * which should be returned. * <li>Type: <b>write</b> ({@link RequestType#WRITE}<br/> * Parameters: <code>param1</code> = MBean name, <code>param2</code> = Attribute name, * <code>param3</code> = value, <code>param4 ... paramN</code> = Inner Path. * The value must be URL encoded (with UTF-8 as charset), and must be convertible into * a data structure</li> * <li>Type: <b>exec</b> ({@link RequestType#EXEC}<br/> * Parameters: <code>param1</code> = MBean name, <code>param2</code> = operation name, * <code>param4 ... paramN</code> = arguments for the operation. * The arguments must be URL encoded (with UTF-8 as charset), and must be convertable into * a data structure</li> * <li>Type: <b>version</b> ({@link RequestType#VERSION}<br/> * Parameters: none * <li>Type: <b>search</b> ({@link RequestType#SEARCH}<br/> * Parameters: <code>param1</code> = MBean name pattern * </ul> * @param pPathInfo path info of HTTP request * @param pProcessingParameters processing parameters. Must not be null/ * @return a newly created {@link JmxRequest} */ public static <R extends JmxRequest> R createGetRequest(String pPathInfo, ProcessingParameters pProcessingParameters) { RequestType type = null; try { String pathInfo = extractPathInfo(pPathInfo, pProcessingParameters); // Get all path elements as a reverse stack Stack<String> elements = EscapeUtil.extractElementsFromPath(pathInfo); // Use version by default if no type is given type = elements.size() != 0 ? RequestType.getTypeByName(elements.pop()) : RequestType.VERSION; // Parse request return (R) getCreator(type).create(elements, pProcessingParameters); } catch (MalformedObjectNameException e) { throw new IllegalArgumentException("Invalid object name. " + e.getMessage(),e); } catch (EmptyStackException exp) { throw new IllegalArgumentException("Invalid arguments in pathinfo " + pPathInfo + (type != null ? " for command " + type : ""),exp); } } /** * Create a single {@link JmxRequest}s from a JSON map representation of a request * * @param pRequestMap JSON representation of a {@link JmxRequest} * @param pProcessingParams additional map of operational parameters. Must not be null. * @return the created {@link JmxRequest} */ public static <R extends JmxRequest> R createPostRequest(Map<String, ?> pRequestMap, ProcessingParameters pProcessingParams) { try { ProcessingParameters paramsMerged = pProcessingParams.mergedParams((Map<String,String>) pRequestMap.get("config")); RequestType type = RequestType.getTypeByName((String) pRequestMap.get("type")); return (R) getCreator(type).create(pRequestMap, paramsMerged); } catch (MalformedObjectNameException e) { throw new IllegalArgumentException("Invalid object name. " + e.getMessage(),e); } } /** * Create a list of {@link JmxRequest}s from a JSON list representing jmx requests * * @param pJsonRequests JSON representation of a list of {@link JmxRequest} * @param pProcessingParams processing options. Must not be null. * @return list with one or more {@link JmxRequest} */ public static List<JmxRequest> createPostRequests(List pJsonRequests, ProcessingParameters pProcessingParams) { List<JmxRequest> ret = new ArrayList<JmxRequest>(); for (Object o : pJsonRequests) { if (!(o instanceof Map)) { throw new IllegalArgumentException("Not a request within the list of requests " + pJsonRequests + ". Expected map, but found: " + o); } ret.add(createPostRequest((Map<String,?>) o,pProcessingParams)); } return ret; } // ======================================================================================================== // Extract path info either from the 'real' URL path, or from an request parameter private static String extractPathInfo(String pPathInfo, ProcessingParameters pProcessingParams) { String pathInfo = pPathInfo; // If no pathinfo is given directly, we look for a query parameter named 'p'. // This variant is helpful, if there are problems with the server mangling // up the pathinfo (e.g. for security concerns, often '/','\',';' and other are not // allowed in encoded form within the pathinfo) if (pProcessingParams != null && (pPathInfo == null || pPathInfo.length() == 0 || pathInfo.matches("^/+$"))) { pathInfo = pProcessingParams.getPathInfo(); } return normalizePathInfo(pathInfo); } // Return always a non-null string and strip of leading slash private static String normalizePathInfo(String pPathInfo) { if (pPathInfo != null && pPathInfo.length() > 0) { return pPathInfo.startsWith("/") ? pPathInfo.substring(1) : pPathInfo; } else { return ""; } } // ================================================================================== // Dedicated creator for the various operations. They are installed as static processors. // Get the request creator for a specific type private static RequestCreator getCreator(RequestType pType) { RequestCreator creator = CREATOR_MAP.get(pType); if (creator == null) { throw new UnsupportedOperationException("Type " + pType + " is not supported (yet)"); } return creator; } private static final Map<RequestType,RequestCreator> CREATOR_MAP; static { CREATOR_MAP = new HashMap<RequestType, RequestCreator>(); CREATOR_MAP.put(RequestType.READ, JmxReadRequest.newCreator()); CREATOR_MAP.put(RequestType.WRITE, JmxWriteRequest.newCreator()); CREATOR_MAP.put(RequestType.EXEC, JmxExecRequest.newCreator()); CREATOR_MAP.put(RequestType.LIST, JmxListRequest.newCreator()); CREATOR_MAP.put(RequestType.VERSION, JmxVersionRequest.newCreator()); CREATOR_MAP.put(RequestType.SEARCH, JmxSearchRequest.newCreator()); } }