/*
 * 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.catalina.util;

import javax.management.MBeanServer;
import javax.management.ObjectName;

import org.apache.catalina.Globals;
import org.apache.catalina.JmxEnabled;
import org.apache.catalina.LifecycleException;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.modeler.Registry;
import org.apache.tomcat.util.res.StringManager;

public abstract class LifecycleMBeanBase extends LifecycleBase implements JmxEnabled {

    private static final Log log = LogFactory.getLog(LifecycleMBeanBase.class);

    private static final StringManager sm = StringManager.getManager("org.apache.catalina.util");


    /* Cache components of the MBean registration. */
    private String domain = null;
    private ObjectName oname = null;

    @Override
    protected void initInternal() throws LifecycleException {
        // If oname is not null then registration has already happened via
        // preRegister().
        if (oname == null) {
            oname = register(this, getObjectNameKeyProperties());
        }
    }


    @Override
    protected void destroyInternal() throws LifecycleException {
        unregister(oname);
    }


    @Override
    public final void setDomain(String domain) {
        this.domain = domain;
    }


    @Override
    public final String getDomain() {
        if (domain == null) {
            domain = getDomainInternal();
        }

        if (domain == null) {
            domain = Globals.DEFAULT_MBEAN_DOMAIN;
        }

        return domain;
    }


    /**
     * Method implemented by subclasses to identify the domain in which MBeans should be registered.
     *
     * @return The name of the domain to use to register MBeans.
     */
    protected abstract String getDomainInternal();


    @Override
    public final ObjectName getObjectName() {
        return oname;
    }


    /**
     * Allow subclasses to specify the key properties component of the {@link ObjectName} that will be used to register
     * this component.
     *
     * @return The string representation of the key properties component of the desired {@link ObjectName}
     */
    protected abstract String getObjectNameKeyProperties();


    /**
     * Utility method to enable subclasses to easily register additional components that don't implement
     * {@link JmxEnabled} with an MBean server. <br>
     * Note: This method should only be used once {@link #initInternal()} has been called and before
     * {@link #destroyInternal()} has been called.
     *
     * @param obj                     The object the register
     * @param objectNameKeyProperties The key properties component of the object name to use to register the object
     *
     * @return The name used to register the object
     */
    protected final ObjectName register(Object obj, String objectNameKeyProperties) {

        // Construct an object name with the right domain
        StringBuilder name = new StringBuilder(getDomain());
        name.append(':');
        name.append(objectNameKeyProperties);

        ObjectName on = null;

        try {
            on = new ObjectName(name.toString());
            Registry.getRegistry(null).registerComponent(obj, on, null);
        } catch (Exception e) {
            log.warn(sm.getString("lifecycleMBeanBase.registerFail", obj, name), e);
        }

        return on;
    }


    /**
     * Utility method to enable subclasses to easily unregister additional components that don't implement
     * {@link JmxEnabled} with an MBean server. <br>
     * Note: This method should only be used once {@link #initInternal()} has been called and before
     * {@link #destroyInternal()} has been called.
     *
     * @param objectNameKeyProperties The key properties component of the object name to use to unregister the object
     */
    protected final void unregister(String objectNameKeyProperties) {
        // Construct an object name with the right domain
        String name = getDomain() + ':' + objectNameKeyProperties;
        Registry.getRegistry(null).unregisterComponent(name);
    }


    /**
     * Utility method to enable subclasses to easily unregister additional components that don't implement
     * {@link JmxEnabled} with an MBean server. <br>
     * Note: This method should only be used once {@link #initInternal()} has been called and before
     * {@link #destroyInternal()} has been called.
     *
     * @param on The name of the component to unregister
     */
    protected final void unregister(ObjectName on) {
        Registry.getRegistry(null).unregisterComponent(on);
    }


    /**
     * Not used - NOOP.
     */
    @Override
    public final void postDeregister() {
        // NOOP
    }


    /**
     * Not used - NOOP.
     */
    @Override
    public final void postRegister(Boolean registrationDone) {
        // NOOP
    }


    /**
     * Not used - NOOP.
     */
    @Override
    public final void preDeregister() throws Exception {
        // NOOP
    }


    @Override
    public final ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception {

        this.oname = name;
        this.domain = name.getDomain().intern();

        return oname;
    }

}
