/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2000-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

/*
 * @(#)MQMBeanReadOnly.java	1.18 06/28/07
 */ 

package com.sun.messaging.jmq.jmsserver.management.mbeans;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Iterator;
import java.util.List;
import java.util.ArrayList;
import java.util.Vector;
import java.util.Enumeration;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.ObjectName;
import javax.management.DynamicMBean;
import javax.management.NotificationBroadcasterSupport;
import javax.management.MBeanInfo;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanConstructorInfo;
import javax.management.MBeanParameterInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanNotificationInfo;
import javax.management.ReflectionException;
import javax.management.RuntimeOperationsException;
import javax.management.AttributeNotFoundException;
import javax.management.MBeanException;
import javax.management.InvalidAttributeValueException;
import com.sun.messaging.jmq.jmsserver.Globals;
import com.sun.messaging.jmq.jmsserver.resources.BrokerResources;
import com.sun.messaging.jmq.jmsserver.management.mbeans.resources.MBeanResources;
import com.sun.messaging.jmq.util.log.Logger;

public abstract class MQMBeanReadOnly extends NotificationBroadcasterSupport 
					implements DynamicMBean {
    int sequenceNumber = 0;
    protected Logger logger = Globals.getLogger();

    private String dClassName = this.getClass().getName();

    protected static final BrokerResources rb = Globals.getBrokerResources();
    protected static final MBeanResources mbr = Globals.getMBeanResources();

    private MBeanInfo dMBeanInfo = null;

    public MQMBeanReadOnly()  {
        buildDynamicMBeanInfo();
    }

    public abstract String getMBeanName();

    public abstract String getMBeanDescription();

    public abstract MBeanAttributeInfo[] getMBeanAttributeInfo();

    public abstract MBeanOperationInfo[] getMBeanOperationInfo();

    public abstract MBeanNotificationInfo[] getMBeanNotificationInfo();

    /**
     * Sets the value of the specified attribute of the Dynamic MBean.
     */
    public void setAttribute(Attribute attribute) 
	throws AttributeNotFoundException,
	       InvalidAttributeValueException,
	       MBeanException, 
	       ReflectionException {

	String name = null;

	if (attribute != null)  {
	    name = attribute.getName();
	}

	/*
	 * Throw exception since there are no settable attributes
	 */
	throw(new AttributeNotFoundException(
			"Attribute " 
			+ name 
			+ " cannot be set in MBean: "
			+ getMBeanName()));
    }

    /**
     * Sets the values of several attributes of the Dynamic MBean, and returns the
     * list of attributes that have been set.
     */
    public AttributeList setAttributes(AttributeList attributes)  {
	if (attributes == null)  {
	    throw new RuntimeOperationsException(
		new IllegalArgumentException(
			"Null attribute list passed to setAttributes()"));
        }
	
	AttributeList resultList = new AttributeList();

	if (attributes.isEmpty())  {
	    return resultList;
	}

	for (Iterator i = attributes.iterator(); i.hasNext();) {
	    Attribute attr = (Attribute) i.next();

	    try  {
		setAttribute(attr);
		String name = attr.getName();
		Object value = getAttribute(name);
		resultList.add(new Attribute(name, value));
	    } catch (Exception e)  {
	        String tmp = "MBean "
		    + getMBeanName()
		    + ": Problem encountered while setting attribute "
		    + attr.getName()
		    + ": "
		    + e.toString();
	        logger.log(Logger.WARNING, tmp, e);
	    }
	}

	return (resultList);
    }

    /**
     * Allows an operation to be invoked on the Dynamic MBean.
     */
	public Object invoke(String operationName, Object[] params, String[] signature) 
				throws MBeanException, ReflectionException {
	if (operationName == null) {
	    throw new RuntimeOperationsException(new IllegalArgumentException(
			"Null Operation name passed to invoke()"));
	}

	MBeanOperationInfo op = getOperationInfo(operationName, signature);
	Method m = null;
	Object ret = null;

	if (op == null)  {
	    /*
	     * If this operation was not defined as an operation in this
	     * MBean, perhaps, it was defined as an 'isIs' attribute.
	     */
	    m = getIsIsMethod(operationName);

	    if (m == null)  {
	        throw new RuntimeOperationsException(new IllegalArgumentException(
			"MBean "
			+ getMBeanName()
			+ ": Invalid operation name and/or signature passed to invoke(): "
			    + operationName)
			);
	    }
	} else  {
	    m = getMethod(op);
	}

	try  {
	    if (m != null)  {
	    	final Method finalM = m;
	    	final Object[] finalParams = params;
	    	final MQMBeanReadOnly receiver = this;
	        try {
	        	ret = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
						public Object run() throws IllegalAccessException, IllegalArgumentException,
								InvocationTargetException {
							return finalM.invoke(receiver, finalParams);
						}
					}
	              );
	        } catch (PrivilegedActionException e) {
	        	throw e.getException();
	        }
	    }
	} catch(Exception e)  {
	    throw (new MBeanException(e, "Exception caught while invoking operation "
			+ operationName
			+ " in MBean "
			+ getMBeanName()));
	}

	return (ret);
    }

    private Method getMethod(MBeanOperationInfo op) throws ReflectionException {
	Method m = null;
	String methodName = null;

	if (op == null)  {
	    return (null);
	}

	try  {
	    Class methodParams[] = null;
	    MBeanParameterInfo params[] = op.getSignature();

	    methodName = op.getName();

	    if ((params != null) && (params.length > 0))  {
	        methodParams = new Class [ params.length ];

	        for (int i = 0; i < params.length; ++i)  {
		    String className = params[i].getType();

		    try  {
		        Class paramClass = Class.forName(className);
	                methodParams[i] = paramClass;
		    } catch (ClassNotFoundException cnfE)  {
	                throw (new ReflectionException(cnfE,
			      "Parameter type/class not found for operation"
			      + methodName
			      + " in MBean "
		              + getMBeanName()));
		    }
	        }
	    }

	    m = this.getClass().getMethod(methodName, methodParams);
	} catch(NoSuchMethodException noSuchE)  {
	    throw (new ReflectionException(noSuchE,
		  "Operation " 
		  + methodName 
		  + " does not exist in MBean"
		  + getMBeanName()));
	} catch(SecurityException se)  {
	    throw (new ReflectionException(se));
	}

	return (m);
    }

    private Method getIsIsMethod(String operationName) throws ReflectionException {
	Method m = null;
	String methodName = null;

	if (operationName == null)  {
	    return (null);
	}

	/*
	 * Check first if this operation name is:
	 *	"is" + <an attribute name>
	 */
	String attr = operationName.substring(2);
	try  {
	    checkIsIsAttribute(attr);
	} catch(Exception e)  {
	    return (null);
	}

	methodName = operationName;

	try  {
	    /*
	     * The "is" method does not have any params
	     */
	    m = this.getClass().getMethod(methodName, null);
	} catch(NoSuchMethodException noSuchE)  {
	    throw (new ReflectionException(noSuchE,
		  "Operation " 
		  + methodName 
		  + " does not exist in MBean"
		  + getMBeanName()));
	} catch(SecurityException se)  {
	    throw (new ReflectionException(se));
	}

	return (m);
    }


    /**
     * Enables the to get the values of several attributes of the Dynamic MBean.
     */
    public AttributeList getAttributes(String[] attributeNames)  {
	if (attributeNames == null) {
	    throw new RuntimeOperationsException(
		new IllegalArgumentException(
			"MBean "
			+ getMBeanName()
			+ ": Null attribute list passed to getAttributes()"));
        }

	AttributeList resultList = new AttributeList();

	if (attributeNames.length == 0)
	    return resultList;
	
	for (int i=0; i < attributeNames.length; i++)  {
	    try  {
	        Object value = getAttribute(attributeNames[i]);
		resultList.add(new Attribute(attributeNames[i], value));
	    } catch (Exception e)  {
	        String tmp = "MBean "
		    + getMBeanName()
		    + ": Problem encountered while getting attribute "
		    + attributeNames[i]
		    + ": "
		    + e.toString();
	        logger.log(Logger.WARNING, tmp, e);
	    }
	}
	
	return (resultList);
    }

    /**
     * Allows the value of the specified attribute of the Dynamic MBean to be obtained.
     */
    public Object getAttribute(String attributeName) throws
				AttributeNotFoundException,
				MBeanException,
				ReflectionException  {

	if (attributeName == null) {
	    throw new RuntimeOperationsException(
		new IllegalArgumentException(
			"MBean "
			+ getMBeanName()
			+ ": Null attribute passed to getAttribute()"));
        }

	checkReadableAttribute(attributeName);

	Method m = null;
	String methodName = "get" + attributeName;
	Object ret = null;

	try  {
	    Class methodParams[] = null;

	    m = this.getClass().getMethod(methodName, methodParams);
        } catch(NoSuchMethodException noSuchE)  {
	    String tmp = "MBean "
		+ getMBeanName()
	        + ": Cannot find method "
                + methodName;
            throw (new ReflectionException(noSuchE, tmp));
        } catch(SecurityException se)  {
            throw (new ReflectionException(se));
        }

	try  {
	    Object params[] = null;

	    ret = m.invoke(this, params);
	} catch(Exception e) { 
	    throw (new MBeanException(e)); 
	}

	return (ret);
    }

    private void checkReadableAttribute(String name) throws
		AttributeNotFoundException {
        MBeanAttributeInfo attrInfo = getAttributeInfo(name);

	if (attrInfo == null)  {
	    throw new AttributeNotFoundException("The attribute "
			+ name
			+ " is not a valid attribute for MBean"
			+ getMBeanName());
	}

	if (!attrInfo.isReadable())  {
	    throw new AttributeNotFoundException("The attribute "
			+ name
			+ " is not a gettable attribute for MBean"
			+ getMBeanName());
	}
    }

    private void checkIsIsAttribute(String name) throws
		AttributeNotFoundException {
        MBeanAttributeInfo attrInfo = getAttributeInfo(name);

	if (attrInfo == null)  {
	    throw new AttributeNotFoundException("The attribute "
			+ name
			+ " is not a valid attribute for MBean"
			+ getMBeanName());
	}

	if (!attrInfo.isIs())  {
	    throw new AttributeNotFoundException("The attribute "
			+ name
			+ " is not an isIs attribute for MBean"
			+ getMBeanName());
	}
    }

    public MBeanAttributeInfo getAttributeInfo(String name)  {
        MBeanAttributeInfo[] attrs = getMBeanAttributeInfo();

	if (attrs == null)  {
	    return (null);
	}

	for (int i = 0; i < attrs.length; ++i)  {
	    MBeanAttributeInfo oneAttr = attrs[i];

	    if (oneAttr.getName().equals(name))  {
		return (oneAttr);
	    }
	}

	return (null);
    }

    public MBeanOperationInfo getOperationInfo(String operationName,
			String[] signature)  {
        MBeanOperationInfo[] ops = getMBeanOperationInfo();

	if (ops == null)  {
	    return (null);
	}

	for (int i = 0; i < ops.length; ++i)  {
	    MBeanOperationInfo oneOp = ops[i];

	    if (oneOp.getName().equals(operationName))  {
		if (operationSignatureOK(oneOp, signature))  {
		    return (oneOp);
		}
	    }
	}

	return (null);
    }

    /*
     * Returns true if the number of parameters in 'signature'
     * and their types match that in 'op'.
     *
     * TBD: instead of making the types match, shouldn't they be
     * assignable ?
     */
    private boolean operationSignatureOK(MBeanOperationInfo op, 
						String[] signature)  {
	if (op == null)  {
	    return (false);
	}

	MBeanParameterInfo params[] = op.getSignature();

	/*
	 * Operation info has no parameters.
	 */
	if (params == null)  {
	    /*
	     * signature requested has params - no match
	     */
	    if (signature != null)  {
		return (false);
	    }

	    return (true);
	}

	/*
	 * Operation info has parameters
	 */

	/*
	 * Return false if signature requested has no params.
	 */
	if (signature == null)  {
	    return (false);
	}

	/*
	 * Return false if number of params don't match.
	 */
	if (params.length != signature.length)  {
	    return (false);
	}

	/*
	 * Compare each and every param
	 */
	for (int i = 0; i < params.length; ++i)  {
	    String paramType = params[i].getType();

	    if (signature[i] == null)  {
		return (false);
	    }

	    if (!signature[i].equals(paramType))  {
		return (false);
	    }
	}

	return (true);
    }

    public MBeanInfo getMBeanInfo() {

	// return the information we want to expose for management:
	// the dMBeanInfo private field has been built at instanciation time,
	return dMBeanInfo;
    }

    public MBeanConstructorInfo[] getMBeanConstructorInfo()  {
	Constructor[] constructors = this.getClass().getConstructors();
        MBeanConstructorInfo[] dConstructors = new MBeanConstructorInfo[1];
	dConstructors[0] = new MBeanConstructorInfo(
				"Constructor", constructors[0]);
	return (dConstructors);
    }

    protected void handleGetterException(String attrName, Throwable t)
					throws MBeanException  {
	String tmp;

	tmp = getMBeanName()
		+ ": Problem encountered while getting attribute "
		+ attrName
		+ ": "
		+ t.toString();
	logger.log(Logger.WARNING, 
		tmp, t);

	throw new MBeanException(new Exception(tmp));
    }

    protected void handleSetterException(String attrName, Throwable t) 
					throws MBeanException  {
	String tmp;

	tmp = getMBeanName()
		+ ": Problem encountered while setting attribute "
		+ attrName
		+ ": "
		+ t.toString();
	logger.log(Logger.WARNING, 
		tmp, t);
	
	throw new MBeanException(new Exception(tmp));
    }

    protected void handleOperationException(String opName, Exception e) 
					throws MBeanException  {
	String tmp;

	tmp = "MBean "
		+ getMBeanName()
		+ ": Problem encountered while invoking operation "
		+ opName
		+ ": "
		+ e.toString();
	logger.log(Logger.WARNING, tmp);
	
	throw new MBeanException(new Exception(tmp));
    }

    /**
     * Build the private dMBeanInfo field,
     * which represents the management interface exposed by the MBean;
     * that is, the set of attributes, constructors, operations and notifications
     * which are available for management. 
     *
     * A reference to the dMBeanInfo object is returned by the getMBeanInfo() method
     * of the DynamicMBean interface. Note that, once constructed, an MBeanInfo object is immutable.
     */
    private void buildDynamicMBeanInfo() {
	dMBeanInfo = new MBeanInfo(dClassName,
				   getMBeanDescription(),
				   getMBeanAttributeInfo(),
				   getMBeanConstructorInfo(),
				   getMBeanOperationInfo(), 
				   getMBeanNotificationInfo());
    }
}
