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
070        m_engine = engine;
071        applicationName = m_engine.getWikiProperties().getProperty("jspwiki.applicationName").trim();
072
073        if( m_mbeanServer != null ) {
074            LOG.info( m_mbeanServer.getClass().getName() );
075            LOG.info( m_mbeanServer.getDefaultDomain() );
076        }
077
078        m_engine.addWikiEventListener( this );
079        initialize();
080    }
081
082    /** {@inheritDoc} */
083    @Override
084    public void initialize() {
085        reload();
086    }
087
088    private String getJMXTitleString( final int title ) {
089        switch( title ) {
090            case AdminBean.CORE:
091                return "Core";
092
093            case AdminBean.EDITOR:
094                return "Editors";
095
096            case AdminBean.UNKNOWN:
097            default:
098                return "Unknown";
099        }
100    }
101
102
103    /**
104     *  Register an AdminBean.  If the AdminBean is also a JMX MBean, it also gets registered to the MBeanServer we've found.
105     *
106     *  @param ab AdminBean to register.
107     */
108    private void registerAdminBean( final AdminBean ab ) {
109        try {
110            if( ab instanceof DynamicMBean && m_mbeanServer != null ) {
111                final ObjectName name = getObjectName( ab );
112                if( !m_mbeanServer.isRegistered( name ) ) {
113                    m_mbeanServer.registerMBean( ab, name );
114                }
115            }
116
117            m_allBeans.add( ab );
118
119            LOG.info( "Registered new admin bean " + ab.getTitle() );
120        } catch( final InstanceAlreadyExistsException e ) {
121            LOG.error( "Admin bean already registered to JMX", e );
122        } catch( final MBeanRegistrationException e ) {
123            LOG.error( "Admin bean cannot be registered to JMX", e );
124        } catch( final NotCompliantMBeanException e ) {
125            LOG.error( "Your admin bean is not very good", e );
126        } catch( final MalformedObjectNameException e ) {
127            LOG.error( "Your admin bean name is not very good", e );
128        } catch( final NullPointerException e ) {
129            LOG.error( "Evil NPE occurred", e );
130        }
131    }
132
133    private ObjectName getObjectName( final AdminBean ab ) throws MalformedObjectNameException {
134        final String component = getJMXTitleString( ab.getType() );
135        final String title     = ab.getTitle();
136        return new ObjectName(String.format("%s:component=%s,name=%s (%s)", Release.APPNAME, component, title, applicationName));
137    }
138
139    /**
140     *  Registers all the beans from a collection of WikiModuleInfos.  If some of the beans fail, logs the message and keeps going to the
141     *  next bean.
142     *
143     *  @param c Collection of WikiModuleInfo instances
144     */
145    private void registerBeans( final Collection< WikiModuleInfo > c ) {
146        for( final WikiModuleInfo wikiModuleInfo : c ) {
147            final String abname = wikiModuleInfo.getAdminBeanClass();
148            try {
149                if( abname != null && !abname.isEmpty() ) {
150                    final AdminBean ab = ClassUtil.buildInstance( abname );
151                    registerAdminBean( ab );
152                }
153            } catch( final ReflectiveOperationException e ) {
154                LOG.error( e.getMessage(), e );
155            }
156        }
157
158    }
159
160    // FIXME: Should unload the beans first.
161    private void reload() {
162        m_allBeans = new ArrayList<>();
163
164        try {
165            registerAdminBean( new CoreBean( m_engine ) );
166            registerAdminBean( new UserBean( m_engine ) );
167            registerAdminBean( new SearchManagerBean( m_engine ) );
168            registerAdminBean( new PluginBean( m_engine ) );
169            registerAdminBean( new FilterBean( m_engine ) );
170        } catch( final NotCompliantMBeanException e ) {
171            LOG.error( e.getMessage(), e );
172        }
173        for( final ModuleManager moduleManager : m_engine.getManagers( ModuleManager.class ) ) {
174            registerBeans( moduleManager.modules() );
175        }
176    }
177
178    /* (non-Javadoc)
179     * @see org.apache.wiki.ui.admin.AdminBeanManager#getAllBeans()
180     */
181    @Override
182    public List< AdminBean > getAllBeans() {
183        if( m_allBeans == null ) {
184            reload();
185        }
186
187        return m_allBeans;
188    }
189
190    /* (non-Javadoc)
191     * @see org.apache.wiki.ui.admin.AdminBeanManager#findBean(java.lang.String)
192     */
193    @Override
194    public AdminBean findBean( final String id ) {
195        return m_allBeans.stream().filter(ab -> ab.getId().equals(id)).findFirst().orElse(null);
196
197    }
198
199    /**
200     *  Provides a JDK 1.5-compliant version of the MBeanServerFactory. This will simply bind to the
201     *  platform MBeanServer.
202     */
203    private static final class MBeanServerFactory15 {
204        private MBeanServerFactory15()
205        {}
206
207        public static MBeanServer getServer() {
208            return ManagementFactory.getPlatformMBeanServer();
209        }
210    }
211
212    /**
213     *  Returns the type identifier for a string type.
214     *
215     *  @param type A type string.
216     *  @return A type value.
217     */
218    @Override
219    public int getTypeFromString( final String type ) {
220        if( "core".equals( type ) ) {
221            return AdminBean.CORE;
222        } else if( "editors".equals( type ) ) {
223            return AdminBean.EDITOR;
224        }
225
226        return AdminBean.UNKNOWN;
227    }
228
229    /* (non-Javadoc)
230     * @see org.apache.wiki.ui.admin.AdminBeanManager#actionPerformed(org.apache.wiki.event.WikiEvent)
231     */
232    @Override
233    public void actionPerformed( final WikiEvent event ) {
234        if( event instanceof WikiEngineEvent ) {
235            if( event.getType() == WikiEngineEvent.SHUTDOWN ) {
236                for( final AdminBean m_allBean : m_allBeans ) {
237                    try {
238                        final ObjectName on = getObjectName( m_allBean );
239                        if( m_mbeanServer.isRegistered( on ) ) {
240                            m_mbeanServer.unregisterMBean( on );
241                            LOG.info( "Unregistered AdminBean " + m_allBean.getTitle() );
242                        }
243                    } catch( final MalformedObjectNameException e ) {
244                        LOG.error( "Malformed object name when unregistering", e );
245                    } catch( final InstanceNotFoundException e ) {
246                        LOG.error( "Object was registered; yet claims that it's not there", e );
247                    } catch( final MBeanRegistrationException e ) {
248                        LOG.error( "Registration exception while unregistering", e );
249                    }
250                }
251            }
252        }
253    }
254
255}