/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. */ package org.apache.sling.jmx.provider.impl; import java.io.UnsupportedEncodingException; import java.lang.management.ManagementFactory; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.TreeMap; import javax.management.Attribute; import javax.management.AttributeList; import javax.management.InstanceNotFoundException; import javax.management.IntrospectionException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanInfo; import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import javax.management.ReflectionException; import javax.servlet.http.HttpServletRequest; import org.apache.felix.scr.annotations.Activate; import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Deactivate; import org.apache.felix.scr.annotations.Properties; import org.apache.felix.scr.annotations.Property; import org.apache.felix.scr.annotations.Service; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceProvider; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.resource.ResourceUtil; import org.apache.sling.commons.osgi.PropertiesUtil; @Component(metatype=true, label="Apache Sling JMX Resource Provider", description="This provider mounts JMX mbeans into the resource tree.") @Service(value = ResourceProvider.class) @Properties({ @Property(name = ResourceProvider.ROOTS, value="/system/sling/monitoring/mbeans", label="Root", description="The mount point of the JMX beans"), @Property(name = ResourceProvider.USE_RESOURCE_ACCESS_SECURITY, boolValue=true, propertyPrivate=true), @Property(name = ResourceProvider.OWNS_ROOTS, boolValue=true, propertyPrivate=true) }) /** * Brief summary of a "good" object name: * * Object names: * - have a domain * - should have a type property * - could have a name property * - additional props are not recommended * * Path to an MBean: * {Domain}/{type property}/{name property}{all other props} * where * {Domain} : is a path consisting of the domain (dots replaced with slashes) * {type property} : is the value of the type property or "{notype}" if no type property is set * {name property} : is the value of the name property or "{noname}" if no name property is set * {all other props} : name/value pairs containing all additional props */ public class JMXResourceProvider implements ResourceProvider { /** Configured root paths, ending with a slash */ private String[] rootsWithSlash; /** Configured root paths, not ending with a slash */ private String[] roots; /** The mbean server. */ private MBeanServer mbeanServer; @Activate protected void activate(final Map<String, Object> props) { final String paths[] = PropertiesUtil.toStringArray(props.get(ResourceProvider.ROOTS)); final List<String> rootsList = new ArrayList<String>(); final List<String> rootsWithSlashList = new ArrayList<String>(); if ( paths != null ) { for(final String p : paths) { if ( p.length() > 0 ) { if ( p.endsWith("/") ) { rootsList.add(p.substring(0, p.length() - 1)); rootsWithSlashList.add(p); } else { rootsList.add(p); rootsWithSlashList.add(p + "/"); } } } } this.rootsWithSlash = rootsWithSlashList.toArray(new String[rootsWithSlashList.size()]); this.roots = rootsList.toArray(new String[rootsList.size()]); this.mbeanServer = ManagementFactory.getPlatformMBeanServer(); } @Deactivate protected void deactivate() { this.mbeanServer = null; } /** * @see org.apache.sling.api.resource.ResourceProvider#getResource(org.apache.sling.api.resource.ResourceResolver, javax.servlet.http.HttpServletRequest, java.lang.String) */ public Resource getResource(final ResourceResolver resourceResolver, final HttpServletRequest request, final String path) { return getResource(resourceResolver, path); } /** * @see org.apache.sling.api.resource.ResourceProvider#getResource(org.apache.sling.api.resource.ResourceResolver, java.lang.String) */ public Resource getResource(final ResourceResolver resourceResolver, final String path) { final PathInfo info = this.parse(path); if ( info != null ) { if ( info.isRoot ) { return new RootResource(resourceResolver, path); } if ( info.mbeanInfo == null ) { final Set<ObjectName> names = this.queryObjectNames(info.pathInfo); if ( names.size() != 0 ) { return new RootResource(resourceResolver, path); } } else { if (info.pathInfo == null ) { return new MBeanResource(this.mbeanServer, resourceResolver, this.convertObjectNameToResourcePath(info.objectName), path, info.mbeanInfo, info.objectName); } if ( info.pathInfo.equals("mbean:attributes") ) { final MBeanResource parent = (MBeanResource)this.getResource(resourceResolver, ResourceUtil.getParent(path)); return new AttributesResource(resourceResolver, path, parent); } if ( info.pathInfo.startsWith("mbean:attributes/") ) { final Resource parentRsrc = this.getResource(resourceResolver, ResourceUtil.getParent(path)); final AttributesResource parentAttributesResource; final MBeanResource parentMBeanResource; if ( parentRsrc instanceof AttributesResource ) { parentAttributesResource = (AttributesResource) parentRsrc; parentMBeanResource = (MBeanResource)parentRsrc.getParent(); } else { final AttributeResource parent; if ( parentRsrc instanceof AttributeResource) { parent = (AttributeResource)parentRsrc; } else { parent = ((MapResource)parentRsrc).getAttributeResource(); } parentAttributesResource = (AttributesResource) parent.getParent(); parentMBeanResource = (MBeanResource) parentAttributesResource.getParent(); } final AttributeList result = parentMBeanResource.getAttributes(); final String attrPath = info.pathInfo.substring("mbean:attributes/".length()); final int pos = attrPath.indexOf('/'); final String attrName; final String subPath; if ( pos == -1 ) { attrName = attrPath; subPath = null; } else { attrName = attrPath.substring(0, pos); subPath = attrPath.substring(pos + 1); } for(final MBeanAttributeInfo mai : info.mbeanInfo.getAttributes()) { if ( mai.getName().equals(attrName) ) { final Iterator iter = result.iterator(); Object value = null; while ( iter.hasNext() && value == null ) { final Attribute a = (Attribute) iter.next(); if ( a.getName().equals(attrName) ) { value = a.getValue(); } } final AttributeResource rsrc = new AttributeResource(resourceResolver, path, mai, value, parentAttributesResource); if ( subPath != null ) { return rsrc.getChildResource(subPath); } return rsrc; } } } } } return null; } private Set<ObjectName> queryObjectNames(final String prefix) { final Set<ObjectName> allNames = this.mbeanServer.queryNames(null, null); Set<ObjectName> names = allNames; if ( prefix != null ) { final String pathPrefix = prefix + '/'; names = new HashSet<ObjectName>(); for(final ObjectName name : allNames) { final String path = this.convertObjectNameToResourcePath(name); if ( path.startsWith(pathPrefix) ) { names.add(name); } } } return names; } /** * @see org.apache.sling.api.resource.ResourceProvider#listChildren(org.apache.sling.api.resource.Resource) */ public Iterator<Resource> listChildren(final Resource parent) { final PathInfo info = this.parse(parent.getPath()); if ( info != null ) { if ( info.isRoot || info.mbeanInfo == null ) { // list all MBeans final Set<ObjectName> names = this.queryObjectNames(info.isRoot ? null : info.pathInfo); final Set<String> filteredNames = new HashSet<String>(); final String prefix = (info.isRoot ? null : info.pathInfo + "/"); for(final ObjectName name : names) { final String path = this.convertObjectNameToResourcePath(name); final String testName = (info.isRoot ? path : path.substring(prefix.length())); final int sep = testName.indexOf('/'); if ( sep == -1 ) { filteredNames.add(":" + name.getCanonicalName()); } else { filteredNames.add(testName.substring(0, sep)); } } final List<String> sortedNames = new ArrayList<String>(filteredNames); Collections.sort(sortedNames); final Iterator<String> iter = sortedNames.iterator(); return new Iterator<Resource>() { private Resource next; { seek(); } private void seek() { while ( iter.hasNext() && this.next == null ) { final String name = iter.next(); if ( name.startsWith(":") ) { try { final ObjectName on = new ObjectName(name.substring(1)); final MBeanInfo info = mbeanServer.getMBeanInfo(on); final String path = convertObjectNameToResourcePath(on); final int sep = path.lastIndexOf('/'); this.next = new MBeanResource(mbeanServer, parent.getResourceResolver(), path, parent.getPath() + "/" + path.substring(sep + 1), info, on); } catch (final IntrospectionException e) { // ignore } catch (final InstanceNotFoundException e) { // ignore } catch (final ReflectionException e) { // ignore } catch (final MalformedObjectNameException e) { // ignore } } else { this.next = new RootResource(parent.getResourceResolver(), parent.getPath() + '/' + name); } } } public boolean hasNext() { return next != null; } public Resource next() { if ( next != null ) { final Resource rsrc = next; this.next = null; seek(); return rsrc; } throw new NoSuchElementException(); } public void remove() { throw new UnsupportedOperationException("remove"); } }; } else { if ( info.pathInfo == null ) { final MBeanResource parentResource; if ( parent instanceof MBeanResource ) { parentResource = (MBeanResource)parent; } else { parentResource = (MBeanResource)this.getResource(parent.getResourceResolver(), parent.getPath()); } final List<Resource> list = new ArrayList<Resource>(); list.add(new AttributesResource(parent.getResourceResolver(), parent.getPath() + "/mbean:attributes", parentResource)); return list.iterator(); } else if ( info.pathInfo.equals("mbean:attributes") ) { final AttributesResource parentResource; if ( parent instanceof AttributesResource ) { parentResource = (AttributesResource)parent; } else { parentResource = (AttributesResource) this.getResource(parent.getResourceResolver(), parent.getPath()); } final MBeanResource parentMBeanResource = (MBeanResource)parentResource.getParent(); final AttributeList result = parentMBeanResource.getAttributes(); final MBeanAttributeInfo[] infos = info.mbeanInfo.getAttributes(); final Map<String, MBeanAttributeInfo> infoMap = new HashMap<String, MBeanAttributeInfo>(); for(final MBeanAttributeInfo i : infos) { infoMap.put(i.getName(), i); } final Iterator iter = result.iterator(); return new Iterator<Resource>() { public void remove() { throw new UnsupportedOperationException("remove"); } public Resource next() { final Attribute attr = (Attribute)iter.next(); return new AttributeResource(parent.getResourceResolver(), parent.getPath() + "/" + attr.getName(), infoMap.get(attr.getName()), attr.getValue(), parentResource); } public boolean hasNext() { return iter.hasNext(); } }; } else if ( info.pathInfo.startsWith("mbean:attributes/") ) { Resource checkParentResource = parent; if ( !(checkParentResource instanceof AttributeResource) && !(checkParentResource instanceof MapResource ) ) { checkParentResource = this.getResource(parent.getResourceResolver(), parent.getPath()); } final AttributeResource parentResource; if ( checkParentResource instanceof AttributeResource ) { parentResource = (AttributeResource)checkParentResource; } else { parentResource = ((MapResource)checkParentResource).getAttributeResource(); } final String attrPath = info.pathInfo.substring("mbean:attributes/".length()); final int pos = attrPath.indexOf('/'); final String subPath; if ( pos == -1 ) { subPath = null; } else { subPath = attrPath.substring(pos + 1); } return parentResource.getChildren(parent.getPath(), subPath); } } } return null; } private static final String MARKER_NOTYPE = "{notype}"; private static final String MARKER_NONAME = "{noname}"; private String encode(final String value) { try { return URLEncoder.encode(value, "UTF-8"); } catch (final UnsupportedEncodingException uee) { // this should never happen, UTF-8 is always supported return value; } } private String decode(final String value) { try { return URLDecoder.decode(value, "UTF-8"); } catch (final UnsupportedEncodingException uee) { // this should never happen, UTF-8 is always supported return value; } } private String convertObjectNameToResourcePath(final ObjectName name) { final StringBuilder sb = new StringBuilder(name.getDomain().replace('.', '/')); sb.append('/'); if ( name.getKeyProperty("type") != null ) { sb.append(encode(name.getKeyProperty("type"))); } else { sb.append(MARKER_NOTYPE); } sb.append('/'); if ( name.getKeyProperty("name") != null ) { sb.append(encode(name.getKeyProperty("name"))); } else { sb.append(MARKER_NONAME); } final TreeMap<String, String> props = new TreeMap<String, String>(name.getKeyPropertyList()); props.remove("name"); props.remove("type"); boolean first = true; for(final Map.Entry<String, String> entry : props.entrySet()) { if ( first ) { first = false; sb.append(':'); } else { sb.append(','); } sb.append(encode(entry.getKey())); sb.append('='); sb.append(encode(entry.getValue())); } return sb.toString(); } private ObjectName convertResourcePathToObjectName(final String path) { final int nameSlash = path.lastIndexOf('/'); if ( nameSlash != -1 ) { final int typeSlash = path.lastIndexOf('/', nameSlash - 1); if ( typeSlash != -1 ) { final String domain = path.substring(0, typeSlash).replace('/', '.'); final String type = decode(path.substring(typeSlash + 1, nameSlash)); final String nameAndProps = path.substring(nameSlash + 1); final int colonPos = nameAndProps.indexOf(':'); final String name; final String props; if ( colonPos == -1 ) { name = decode(nameAndProps); props = null; } else { name = decode(nameAndProps.substring(0, colonPos)); props = nameAndProps.substring(colonPos + 1); } final StringBuilder sb = new StringBuilder(); sb.append(domain); sb.append(':'); boolean hasProps = false; if ( !MARKER_NOTYPE.equals(type)) { sb.append("type="); sb.append(type); hasProps = true; } if ( !MARKER_NONAME.equals(name) ) { if ( hasProps ) { sb.append(","); } sb.append("name="); sb.append(name); hasProps = true; } if ( props != null ) { final String[] propArray = props.split(","); for(final String keyValue : propArray) { if ( hasProps ) { sb.append(","); } final int pos = keyValue.indexOf('='); if ( pos == -1 ) { return null; } sb.append(decode(keyValue.substring(0, pos))); sb.append('='); sb.append(decode(keyValue.substring(pos+1))); } } try { return new ObjectName(sb.toString()); } catch (final MalformedObjectNameException e) { // ignore } } } return null; } public final static class PathInfo { public final boolean isRoot; public final String pathInfo; public ObjectName objectName; public MBeanInfo mbeanInfo; public PathInfo(final boolean isRoot) { this.isRoot = isRoot; this.pathInfo = null; } public PathInfo(final String info) { this.isRoot = false; this.pathInfo = info; } } /** * Parse the path */ private PathInfo parse(final String path) { for(final String root : this.rootsWithSlash) { if ( path.startsWith(root) ) { final String subPath = path.substring(root.length()); if ( subPath.length() == 0 ) { return new PathInfo(true); } // MBean name / path String checkPath = subPath; String pathInfo = null; ObjectName objectName = null; MBeanInfo mbi = null; while ( checkPath.length() > 0 && mbi == null ) { try { objectName = this.convertResourcePathToObjectName(checkPath); if ( objectName != null ) { mbi = this.mbeanServer.getMBeanInfo(objectName); } } catch (final IntrospectionException e) { // ignore } catch (final InstanceNotFoundException e) { // ignore } catch (final ReflectionException e) { // ignore } if ( mbi == null ) { final int sep = checkPath.lastIndexOf('/'); if ( sep == -1 ) { checkPath = ""; pathInfo = subPath; } else { checkPath = checkPath.substring(0, sep); pathInfo = subPath.substring(sep + 1); } } } final PathInfo info = new PathInfo(pathInfo); if ( mbi != null ) { info.objectName = objectName; info.mbeanInfo = mbi; } return info; } } for(final String root : this.roots) { if ( path.equals(root) ) { return new PathInfo(true); } } return null; } }