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}