001/*
002    Licensed to the Apache Software Foundation (ASF) under one
003    or more contributor license agreements.  See the NOTICE file
004    distributed with this work for additional information
005    regarding copyright ownership.  The ASF licenses this file
006    to you under the Apache License, Version 2.0 (the
007    "License"); you may not use this file except in compliance
008    with the License.  You may obtain a copy of the License at
009
010       http://www.apache.org/licenses/LICENSE-2.0
011
012    Unless required by applicable law or agreed to in writing,
013    software distributed under the License is distributed on an
014    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015    KIND, either express or implied.  See the License for the
016    specific language governing permissions and limitations
017    under the License.
018 */
019package org.apache.wiki.ui.admin;
020
021import org.apache.logging.log4j.LogManager;
022import org.apache.logging.log4j.Logger;
023import org.apache.wiki.api.Release;
024import org.apache.wiki.api.core.Engine;
025import org.apache.wiki.event.WikiEngineEvent;
026import org.apache.wiki.event.WikiEvent;
027import org.apache.wiki.event.WikiEventListener;
028import org.apache.wiki.modules.ModuleManager;
029import org.apache.wiki.modules.WikiModuleInfo;
030import org.apache.wiki.ui.admin.beans.CoreBean;
031import org.apache.wiki.ui.admin.beans.FilterBean;
032import org.apache.wiki.ui.admin.beans.PluginBean;
033import org.apache.wiki.ui.admin.beans.SearchManagerBean;
034import org.apache.wiki.ui.admin.beans.UserBean;
035import org.apache.wiki.util.ClassUtil;
036
037import javax.management.DynamicMBean;
038import javax.management.InstanceAlreadyExistsException;
039import javax.management.InstanceNotFoundException;
040import javax.management.MBeanRegistrationException;
041import javax.management.MBeanServer;
042import javax.management.MalformedObjectNameException;
043import javax.management.NotCompliantMBeanException;
044import javax.management.ObjectName;
045import java.lang.management.ManagementFactory;
046import java.util.ArrayList;
047import java.util.Collection;
048import java.util.List;
049
050
051/**
052 *  Provides a manager class for all AdminBeans within JSPWiki.  This class also manages registration for any
053 *  AdminBean which is also a JMX bean.
054 *
055 *  @since  2.5.52
056 */
057public class DefaultAdminBeanManager implements WikiEventListener, AdminBeanManager {
058
059    private final Engine m_engine;
060    private final String applicationName;
061    private ArrayList< AdminBean > m_allBeans;
062    private final MBeanServer m_mbeanServer;
063
064    private static final Logger LOG = LogManager.getLogger( DefaultAdminBeanManager.class );
065
066    public DefaultAdminBeanManager( final Engine engine ) {
067        LOG.info("Using JDK 1.5 Platform MBeanServer");
068        m_mbeanServer = MBeanServerFactory15.getServer();
069        m_engine = engine;
070        applicationName = m_engine.getWikiProperties().getProperty("jspwiki.applicationName").trim();
071
072        if( m_mbeanServer != null ) {
073            LOG.info( m_mbeanServer.getClass().getName() );
074            LOG.info( m_mbeanServer.getDefaultDomain() );
075        }
076
077        m_engine.addWikiEventListener( this );
078        initialize();
079    }
080
081    /** {@inheritDoc} */
082    @Override
083    public void initialize() {
084        reload();
085    }
086
087    private String getJMXTitleString( final int title ) {
088        switch( title ) {
089            case AdminBean.CORE:
090                return "Core";
091
092            case AdminBean.EDITOR:
093                return "Editors";
094
095            case AdminBean.UNKNOWN:
096            default:
097                return "Unknown";
098        }
099    }
100
101
102    /**
103     *  Register an AdminBean.  If the AdminBean is also a JMX MBean, it also gets registered to the MBeanServer we've found.
104     *
105     *  @param ab AdminBean to register.
106     */
107    private void registerAdminBean( final AdminBean ab ) {
108        try {
109            if( ab instanceof DynamicMBean && m_mbeanServer != null ) {
110                final ObjectName name = getObjectName( ab );
111                if( !m_mbeanServer.isRegistered( name ) ) {
112                    m_mbeanServer.registerMBean( ab, name );
113                }
114            }
115
116            m_allBeans.add( ab );
117
118            LOG.info( "Registered new admin bean " + ab.getTitle() );
119        } catch( final InstanceAlreadyExistsException e ) {
120            LOG.error( "Admin bean already registered to JMX", e );
121        } catch( final MBeanRegistrationException e ) {
122            LOG.error( "Admin bean cannot be registered to JMX", e );
123        } catch( final NotCompliantMBeanException e ) {
124            LOG.error( "Your admin bean is not very good", e );
125        } catch( final MalformedObjectNameException e ) {
126            LOG.error( "Your admin bean name is not very good", e );
127        } catch( final NullPointerException e ) {
128            LOG.error( "Evil NPE occurred", e );
129        }
130    }
131    
132    private void unregisterAdminBean( final AdminBean ab ) {
133        try {
134            if( ab instanceof DynamicMBean && m_mbeanServer != null ) {
135                final ObjectName name = getObjectName( ab );
136                if( m_mbeanServer.isRegistered( name ) ) {
137                    m_mbeanServer.unregisterMBean( name );
138                }
139            }
140
141            m_allBeans.add( ab );
142
143            LOG.info( "Registered new admin bean " + ab.getTitle() );
144        } catch( final MBeanRegistrationException e ) {
145            LOG.error( "Admin bean cannot be registered to JMX", e );
146        } catch( final MalformedObjectNameException e ) {
147            LOG.error( "Your admin bean name is not very good", e );
148        } catch( final NullPointerException e ) {
149            LOG.error( "Evil NPE occurred", e );
150        } catch (InstanceNotFoundException ex) {
151            LOG.error( "Failed to unregister mbean " + ab.getClass().getSimpleName(), ex );
152        }
153    }
154
155    private ObjectName getObjectName( final AdminBean ab ) throws MalformedObjectNameException {
156        final String component = getJMXTitleString( ab.getType() );
157        final String title     = ab.getTitle();
158        return new ObjectName( String.format( "%s:component=%s,name=%s (%s)", Release.APPNAME, component, title, applicationName ) );
159    }
160
161    /**
162     *  Registers all the beans from a collection of WikiModuleInfos.  If some of the beans fail, logs the message and keeps going to the
163     *  next bean.
164     *
165     *  @param c Collection of WikiModuleInfo instances
166     */
167    private void registerBeans( final Collection< WikiModuleInfo > c ) {
168        for( final WikiModuleInfo wikiModuleInfo : c ) {
169            final String abname = wikiModuleInfo.getAdminBeanClass();
170            try {
171                if( abname != null && !abname.isEmpty() ) {
172                    final AdminBean ab = ClassUtil.buildInstance( abname );
173                    registerAdminBean( ab );
174                }
175            } catch( final ReflectiveOperationException e ) {
176                LOG.error( e.getMessage(), e );
177            }
178        }
179
180    }
181
182    
183    private void reload() {
184        //unload the beans first.
185        if (m_allBeans != null) {
186            for (AdminBean b : m_allBeans) {
187                unregisterAdminBean(b);
188            }
189        }
190        m_allBeans = new ArrayList<>();
191
192        try {
193            registerAdminBean( new CoreBean( m_engine ) );
194            registerAdminBean( new UserBean( m_engine ) );
195            registerAdminBean( new SearchManagerBean( m_engine ) );
196            registerAdminBean( new PluginBean( m_engine ) );
197            registerAdminBean( new FilterBean( m_engine ) );
198        } catch( final NotCompliantMBeanException e ) {
199            LOG.error( e.getMessage(), e );
200        }
201        for( final ModuleManager moduleManager : m_engine.getManagers( ModuleManager.class ) ) {
202            registerBeans( moduleManager.modules() );
203        }
204    }
205
206    /* (non-Javadoc)
207     * @see org.apache.wiki.ui.admin.AdminBeanManager#getAllBeans()
208     */
209    @Override
210    public List< AdminBean > getAllBeans() {
211        if( m_allBeans == null ) {
212            reload();
213        }
214
215        return m_allBeans;
216    }
217
218    /* (non-Javadoc)
219     * @see org.apache.wiki.ui.admin.AdminBeanManager#findBean(java.lang.String)
220     */
221    @Override
222    public AdminBean findBean( final String id ) {
223        return m_allBeans.stream().filter(ab -> ab.getId().equals(id)).findFirst().orElse(null);
224
225    }
226
227    /**
228     *  Provides a JDK 1.5-compliant version of the MBeanServerFactory. This will simply bind to the
229     *  platform MBeanServer.
230     */
231    private static final class MBeanServerFactory15 {
232        private MBeanServerFactory15()
233        {}
234
235        public static MBeanServer getServer() {
236            try {
237                return ManagementFactory.getPlatformMBeanServer();
238            } catch( final Exception e ) {
239                LOG.error( "Unable to obtain platform MBeanServer, JMX admin beans will not be available", e );
240                return null;
241            }
242        }
243    }
244
245    /**
246     *  Returns the type identifier for a string type.
247     *
248     *  @param type A type string.
249     *  @return A type value.
250     */
251    @Override
252    public int getTypeFromString( final String type ) {
253        if( "core".equals( type ) ) {
254            return AdminBean.CORE;
255        } else if( "editors".equals( type ) ) {
256            return AdminBean.EDITOR;
257        }
258
259        return AdminBean.UNKNOWN;
260    }
261
262    /* (non-Javadoc)
263     * @see org.apache.wiki.ui.admin.AdminBeanManager#actionPerformed(org.apache.wiki.event.WikiEvent)
264     */
265    @Override
266    public void actionPerformed( final WikiEvent event ) {
267        if( m_mbeanServer != null ) {
268            if( event instanceof WikiEngineEvent ) {
269                if( event.getType() == WikiEngineEvent.SHUTDOWN ) {
270                    for( final AdminBean m_allBean : m_allBeans ) {
271                        try {
272                            final ObjectName on = getObjectName( m_allBean );
273                            if( m_mbeanServer.isRegistered( on ) ) {
274                                m_mbeanServer.unregisterMBean( on );
275                                LOG.info( "Unregistered AdminBean " + m_allBean.getTitle() );
276                            }
277                        } catch( final MalformedObjectNameException e ) {
278                            LOG.error( "Malformed object name when unregistering", e );
279                        } catch( final InstanceNotFoundException e ) {
280                            LOG.error( "Object was registered; yet claims that it's not there", e );
281                        } catch( final MBeanRegistrationException e ) {
282                            LOG.error( "Registration exception while unregistering", e );
283                        }
284                    }
285                }
286            }
287        }
288    }
289
290}