001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.wicket.jmx; 018 019import java.lang.management.ManagementFactory; 020import java.util.ArrayList; 021import java.util.List; 022import java.util.concurrent.Callable; 023 024import javax.management.Attribute; 025import javax.management.AttributeList; 026import javax.management.InstanceAlreadyExistsException; 027import javax.management.InstanceNotFoundException; 028import javax.management.MBeanRegistrationException; 029import javax.management.MBeanServer; 030import javax.management.MBeanServerFactory; 031import javax.management.MalformedObjectNameException; 032import javax.management.NotCompliantMBeanException; 033import javax.management.ObjectName; 034import javax.management.StandardMBean; 035 036import org.apache.wicket.IInitializer; 037import org.apache.wicket.ThreadContext; 038import org.apache.wicket.WicketRuntimeException; 039import org.apache.wicket.jmx.wrapper.Application; 040import org.apache.wicket.jmx.wrapper.ApplicationSettings; 041import org.apache.wicket.jmx.wrapper.DebugSettings; 042import org.apache.wicket.jmx.wrapper.MarkupSettings; 043import org.apache.wicket.jmx.wrapper.PageSettings; 044import org.apache.wicket.jmx.wrapper.RequestCycleSettings; 045import org.apache.wicket.jmx.wrapper.RequestLogger; 046import org.apache.wicket.jmx.wrapper.ResourceSettings; 047import org.apache.wicket.jmx.wrapper.SecuritySettings; 048import org.apache.wicket.jmx.wrapper.SessionSettings; 049import org.apache.wicket.jmx.wrapper.StoreSettings; 050import org.slf4j.Logger; 051import org.slf4j.LoggerFactory; 052 053 054/** 055 * Registers Wicket's MBeans. 056 * <p> 057 * Users can specify the MBeanServer implementation in which to register the MBeans by setting the 058 * <code>org.apache.wicket.mbean.server.agentid</code> property to the agent id of the MBeanServer 059 * implementation they want, or by setting <code>org.apache.wicket.mbean.server.class</code> to the 060 * mbean server class they want (if both are provided, and the agent id returns a server, that one 061 * is used). This initializer will log an error when no mbean server with the provided agent id can 062 * be found, and will then fall back to use the platform mbean server. When no agent id is provided, 063 * the platform mbean server will be used. 064 * 065 * @author eelcohillenius 066 * @author David Hosier 067 */ 068public class Initializer implements IInitializer 069{ 070 private static Logger log = LoggerFactory.getLogger(Initializer.class); 071 072 // It's best to store a reference to the MBeanServer rather than getting it 073 // over and over 074 private MBeanServer mbeanServer = null; 075 076 /** 077 * List of registered names 078 */ 079 private final List<ObjectName> registered = new ArrayList<>(); 080 081 @Override 082 public void destroy(final org.apache.wicket.Application application) 083 { 084 for (ObjectName objectName : registered) 085 { 086 try 087 { 088 mbeanServer.unregisterMBean(objectName); 089 } 090 catch (InstanceNotFoundException | MBeanRegistrationException e) 091 { 092 log.error(e.getMessage(), e); 093 } 094 } 095 } 096 097 @Override 098 public void init(final org.apache.wicket.Application application) 099 { 100 try 101 { 102 String name = application.getName(); 103 104 String agentId = null; 105 try 106 { 107 agentId = System.getProperty("wicket.mbean.server.agentid"); 108 } 109 catch (SecurityException e) 110 { 111 // Ignore - we're not allowed to read this property. 112 log.warn("not allowed to read property wicket.mbean.server.agentid due to security settings; ignoring"); 113 } 114 if (agentId != null) 115 { 116 ArrayList<MBeanServer> mbeanServers = MBeanServerFactory.findMBeanServer(agentId); 117 if (!mbeanServers.isEmpty()) 118 { 119 mbeanServer = mbeanServers.get(0); // get first 120 } 121 else 122 { 123 log.error("unable to find mbean server with agent id " + agentId); 124 } 125 } 126 if (mbeanServer == null) 127 { 128 String impl = null; 129 try 130 { 131 impl = System.getProperty("wicket.mbean.server.class"); 132 } 133 catch (SecurityException e) 134 { 135 // Ignore - we're not allowed to read this property. 136 log.warn("not allowed to read property wicket.mbean.server.class due to security settings; ignoring"); 137 } 138 if (impl != null) 139 { 140 ArrayList<MBeanServer> mbeanServers = MBeanServerFactory.findMBeanServer(null); 141 if (!mbeanServers.isEmpty()) 142 { 143 for (MBeanServer mbs : mbeanServers) 144 { 145 if (mbs.getClass().getName().equals(impl)) 146 { 147 mbeanServer = mbs; 148 break; 149 } 150 } 151 } 152 if (mbeanServer == null) 153 { 154 log.error("unable to find mbean server of type '{}'", impl); 155 } 156 } 157 } 158 if (mbeanServer == null) 159 { 160 mbeanServer = ManagementFactory.getPlatformMBeanServer(); 161 // never null 162 } 163 164 log.info("registering Wicket mbeans with server '{}'", mbeanServer); 165 166 // register top level application object, but first check whether 167 // multiple instances of the same application (name) are running and 168 // if so adjust the name 169 String domain = "org.apache.wicket.app." + name; 170 ObjectName appBeanName = new ObjectName(domain + ":type=Application"); 171 String tempDomain = domain; 172 int i = 0; 173 while (mbeanServer.isRegistered(appBeanName)) 174 { 175 tempDomain = name + "-" + i++; 176 appBeanName = new ObjectName(tempDomain + ":type=Application"); 177 } 178 domain = tempDomain; 179 180 Application appBean = new Application(application); 181 register(application, appBean, appBeanName); 182 183 register(application, new ApplicationSettings(application), new ObjectName(domain 184 + ":type=Application,name=ApplicationSettings")); 185 register(application, new DebugSettings(application), new ObjectName(domain 186 + ":type=Application,name=DebugSettings")); 187 register(application, new MarkupSettings(application), new ObjectName(domain 188 + ":type=Application,name=MarkupSettings")); 189 register(application, new ResourceSettings(application), new ObjectName(domain 190 + ":type=Application,name=ResourceSettings")); 191 register(application, new PageSettings(application), new ObjectName(domain 192 + ":type=Application,name=PageSettings")); 193 register(application, new RequestCycleSettings(application), new ObjectName(domain 194 + ":type=Application,name=RequestCycleSettings")); 195 register(application, new SecuritySettings(application), new ObjectName(domain 196 + ":type=Application,name=SecuritySettings")); 197 register(application, new SessionSettings(application), new ObjectName(domain 198 + ":type=Application,name=SessionSettings")); 199 register(application, new StoreSettings(application), new ObjectName(domain 200 + ":type=Application,name=StoreSettings")); 201 202 RequestLogger sessionsBean = new RequestLogger(application); 203 ObjectName sessionsBeanName = new ObjectName(domain + ":type=RequestLogger"); 204 register(application, sessionsBean, sessionsBeanName); 205 } 206 catch (MalformedObjectNameException | InstanceAlreadyExistsException | 207 MBeanRegistrationException | NotCompliantMBeanException e) 208 { 209 throw new WicketRuntimeException(e); 210 } 211 } 212 213 @Override 214 public String toString() 215 { 216 return "Wicket JMX initializer"; 217 } 218 219 /** 220 * Register MBean. 221 * 222 * @param o 223 * MBean 224 * @param objectName 225 * Object name 226 * 227 * @throws NotCompliantMBeanException 228 * @throws MBeanRegistrationException 229 * @throws InstanceAlreadyExistsException 230 */ 231 private <T> void register(final org.apache.wicket.Application application, final T o, 232 final ObjectName objectName) throws InstanceAlreadyExistsException, 233 MBeanRegistrationException, NotCompliantMBeanException 234 { 235 StandardMBean bean = new StandardMBean(o, (Class<T>)o.getClass().getInterfaces()[0]) { 236 @Override 237 public Object getAttribute(String attribute) 238 { 239 return withApplication(() -> super.getAttribute(attribute)); 240 } 241 242 @Override 243 public void setAttribute(Attribute attribute) 244 { 245 withApplication(() -> { 246 super.setAttribute(attribute); 247 return null; 248 }); 249 } 250 251 @Override 252 public AttributeList setAttributes(AttributeList attributes) 253 { 254 return withApplication(() -> super.setAttributes(attributes)); 255 } 256 257 @Override 258 public Object invoke(String actionName, Object[] params, String[] signature) 259 { 260 return withApplication(() -> super.invoke(actionName, params, signature)); 261 } 262 263 private <R> R withApplication(Callable<R> callable) 264 { 265 boolean existed = ThreadContext.exists(); 266 267 if (existed == false) 268 { 269 ThreadContext.setApplication(application); 270 } 271 272 try 273 { 274 return callable.call(); 275 } 276 catch (Exception ex) 277 { 278 throw new WicketRuntimeException(ex); 279 } 280 finally 281 { 282 if (existed == false) 283 { 284 ThreadContext.detach(); 285 } 286 } 287 } 288 }; 289 mbeanServer.registerMBean(bean, objectName); 290 registered.add(objectName); 291 } 292}