/*
 * 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.
 */

/*
 * @(#)JMSAdminImpl.java	1.39 07/11/07
 */ 

package com.sun.messaging.jmq.admin.jmsspi;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.jms.Connection;
import javax.jms.DeliveryMode;
import javax.jms.ExceptionListener;
import javax.jms.InvalidSelectorException;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueReceiver;
import javax.jms.QueueSender;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.TemporaryQueue;

import com.sun.messaging.AdministeredObject;
import com.sun.messaging.ConnectionConfiguration;
import com.sun.messaging.DestinationConfiguration;
import com.sun.messaging.QueueConnectionFactory;
import com.sun.messaging.jmq.ClientConstants;
import com.sun.messaging.jmq.Version;
import com.sun.messaging.jmq.admin.apps.broker.BrokerCmdOptions;
import com.sun.messaging.jmq.admin.bkrutil.BrokerConstants;
import com.sun.messaging.jmq.admin.resources.AdminResources;
import com.sun.messaging.jmq.admin.util.Globals;
import com.sun.messaging.jmq.admin.util.JMSObjFactory;
import com.sun.messaging.jmq.jmsclient.ConnectionImpl;
import com.sun.messaging.jmq.jmsclient.JMSXAWrappedConnectionFactoryImpl;
import com.sun.messaging.jmq.jmsspi.JMSAdmin;
import com.sun.messaging.jmq.jmsspi.PropertiesHolder;
import com.sun.messaging.jmq.util.BrokerExitCode;
import com.sun.messaging.jmq.util.DestType;
import com.sun.messaging.jmq.util.admin.DestinationInfo;
import com.sun.messaging.jmq.util.admin.MessageType;


public class JMSAdminImpl implements JMSAdmin, ExceptionListener {

	private static AdminResources ar = Globals.getAdminResources();

    public static final long            DEFAULT_TIMEOUT = 5000;

    private String adminuserName = null;
    private String adminPassword = null;
    private String fallbackDBPassword = null;
    private String hadbPassword=null;
    private String mysqlPassword = null;
    private String keystorePassword = null;
    private String ldapPassword=null;
    String bridgeAdminPassword=null;
    
    private long timeout = -1;

    private QueueConnectionFactory      qcf;
    private QueueConnection             connection;
    private QueueSession                session;
    private Queue                       requestQueue;
    private TemporaryQueue              replyQueue;
    private QueueSender                 sender;
    protected QueueReceiver             receiver;

    private AdministeredObject		tmpDestination = null;
    private AdministeredObject		tmpConnFactory = null;

    private boolean			secureTransportUsed = false;

    // When this class is used to start a broker, these properties are passed to the broker
	private PropertiesHolder brokerPropertiesHolder;
    
    // use this logger name as enabling Glassfish jms logging uses loggers of the form javax.resourceadapter.mqjmsra    												
    protected static transient final String loggerName = "javax.resourceadapter.mqjmsra.lifecycle";
    protected static transient final Logger logger = Logger.getLogger(loggerName);

    /*
     * This class should only be instantiated via JMSAdminFactoryImpl.
     * REVISIT: hard-coded error messages
     */
    protected JMSAdminImpl(Properties connectionProps, PropertiesHolder brokerPropertiesHolder, String username, String adminPassword)
			throws JMSException {

		this.timeout = DEFAULT_TIMEOUT;
		this.adminuserName = username;
		this.adminPassword = adminPassword;
		this.brokerPropertiesHolder = brokerPropertiesHolder;

		createFactory(connectionProps); // init the qcf !!

		String connType = connectionProps.getProperty(ConnectionConfiguration.imqConnectionType, "");

		if (connType.equals("TLS") || connType.equals("SSL")) {
			secureTransportUsed = true;
		}
	}

    /**
     * Return the SPI version
     */  
    public String getVersion()  {
	Version version = new Version();
	return (version.getJMSAdminSpiVersion());
    }

    /**
     * Create a ConnectionFactory administered object
     *
     * @param type        Either QUEUE or TOPIC.
     * @param properties  Connection specific properties.
     * @return New created ConnectionFactory administered object.
     * @exception JMSException thrown if connectionFactory could not be created.
     */
    public Object createConnectionFactoryObject(int type, 
						java.util.Map properties)
           throws JMSException {

	Object cf = null;
	Properties tmpProps = getProperties(properties);

	fillDefaultConnectionFactoryProperties(tmpProps);

	if (type == TOPIC) {
	    cf = JMSObjFactory.createTopicConnectionFactory(tmpProps);
	} else if (type == QUEUE) {
	    cf = JMSObjFactory.createQueueConnectionFactory(tmpProps);
	} else {
	    throw new javax.jms.JMSException(ar.getKString(ar.X_JMSSPI_INVALID_DOMAIN_TYPE));
	}
	return cf;
    }

    /**
     * Create a XAConnectionFactory administered object
     *
     * @param type        Either QUEUE or TOPIC.
     * @param properties Connection specific properties.
     * @return New created JMSXAConnectionFactory administered object.
     *                     ^^^^^
     * @exception JMSException thrown if XAConnectionFactory could not be 
     * created.
     */
    public Object createXAConnectionFactoryObject(int type, 
						  java.util.Map properties)
           throws JMSException {

        Object xcf = null;
        Properties tmpProps = getProperties(properties);

		fillDefaultConnectionFactoryProperties(tmpProps);

        if (type == TOPIC) {
	    xcf = new 
	    com.sun.messaging.jmq.jmsclient.JMSXATopicConnectionFactoryImpl();
	    setProperties((AdministeredObject)xcf, tmpProps);

	} else if (type == QUEUE) {
	    xcf = new 
	    com.sun.messaging.jmq.jmsclient.JMSXAQueueConnectionFactoryImpl();
	    setProperties((AdministeredObject)xcf, tmpProps);

	} else {
	    throw new javax.jms.JMSException(ar.getKString(ar.X_JMSSPI_INVALID_DOMAIN_TYPE));
        }
        return xcf;
    }


    /**
     * Create a Destination administered object
     *
     * @param destinationName The destination name.
     * @param type        Either QUEUE or TOPIC.
     * @param properties destination specific properties.
     * @return New created Destination administered object.
     * @exception JMSException thrown if destination object could not be 
     * created.
     */
    public Object createDestinationObject(String destinationName,
                                          int type,
                                          java.util.Map properties)
                                          throws JMSException {
        Map props = properties;
	if (props == null) props = new Properties();
	props.put(DestinationConfiguration.imqDestinationName, destinationName);
        return createDestinationObject(type, props);
    }

    /**
     * Create a Destination administered object. The destination name
     * is assumed in properties like imqDestinationName=xxxxx
     *
     * @param type        Either QUEUE or TOPIC.
     * @param properties destination specific properties.
     * @return New created Destination administered object.
     * @exception JMSException thrown if destination object could not be 
     * created.
     */
    public Object createDestinationObject(int type,
                                          java.util.Map properties)
                                          throws JMSException {
	if (properties == null || 
            properties.get(DestinationConfiguration.imqDestinationName) == null)
            throw new JMSException(ar.getKString(ar.X_JMSSPI_NO_DESTINATION_NAME));
	Object dest = null;

        Properties tmpProps = getProperties(properties);

        if (type == TOPIC) {
	    dest = JMSObjFactory.createTopic(tmpProps);

	} else if (type == QUEUE) {
	    dest = JMSObjFactory.createQueue(tmpProps);

	} else {
	    throw new javax.jms.JMSException(ar.getKString(ar.X_JMSSPI_INVALID_DOMAIN_TYPE));
	}
        return dest;
    }

    /**
     * Wrap a standard JMS ConnectionFactory administered object 
     *
     * @param obj a XAQueue/TopicConnectionFactory or Queue/TopicConnectionFactory object   
     * @return a JMSXAConnectionFactory object
     *                     ^^^^^
     * @exception JMSException if fail to wrap
     */
    public Object wrapJMSConnectionFactoryObject(Object obj) throws JMSException {
        if (obj instanceof javax.jms.XAQueueConnectionFactory)
            return new JMSXAWrappedConnectionFactoryImpl((javax.jms.XAQueueConnectionFactory)obj);
        if (obj instanceof javax.jms.XATopicConnectionFactory)
            return new JMSXAWrappedConnectionFactoryImpl((javax.jms.XATopicConnectionFactory)obj);
        if (obj instanceof javax.jms.QueueConnectionFactory)
            return new JMSXAWrappedConnectionFactoryImpl((javax.jms.QueueConnectionFactory)obj);
        if (obj instanceof javax.jms.TopicConnectionFactory)
            return new JMSXAWrappedConnectionFactoryImpl((javax.jms.TopicConnectionFactory)obj);

        throw new JMSException(ar.getKString(ar.X_JMSSPI_INVALID_OBJECT_TYPE));
    }

    public void validateJMSSelector(String selector) throws JMSException {
        com.sun.messaging.jmq.jmsselector.JMSSelector jmsselector = 
                      new com.sun.messaging.jmq.jmsselector.JMSSelector();
        try {
            jmsselector.validateSelectorPattern(selector);
        }
        catch (com.sun.messaging.jmq.jmsselector.InvalidJMSSelectorException e) {
            throw new InvalidSelectorException(e.getMessage());            
        }
        catch (Exception e) {
            throw new JMSException(e.getMessage());            
        }
    }

    /**
     * @return the client-id property name
     */
    public String clientIDPropertyName() {
        return ConnectionConfiguration.imqConfiguredClientID;
    }

    /**
     * Returns a map of all the properties that a destination
     * object has. 
     * <P>
     * For each property name returned, a corresponding label that can 
     * be used for output display purposes is also returned.
     * The Map returned contains the property names and property
     * labels as key-value pairs. This information can be used to display 
     * more readable output containing property labels and not property
     * names.
     *
     * @param type Either QUEUE or TOPIC.
     * @return Map containing attribute value pairs of property names
     *		and their display labels.
     */
    public Map getAllDestinationObjectProperties(int type)
						throws JMSException  {
	Properties	ret;

	if (tmpDestination == null)  {
	    tmpDestination = (AdministeredObject)JMSObjFactory.createTopic(new Properties());
	}

	ret = new Properties();

	for (Enumeration e = tmpDestination.enumeratePropertyNames(); e.hasMoreElements();) {
	    String propName = (String)e.nextElement();
	    String propLabel = tmpDestination.getPropertyLabel(propName);
	    ret.setProperty(propName, propLabel);
	}

	return (ret);
    }

    /**
     * Returns a subset of all the properties that a destination 
     * object has. 
     * <P>
     * This collection of properties can be used to construct a user
     * interface where not all of the object's properties will be 
     * displayed, and only a selected few (more important) ones are.
     * <P>
     * For each property name returned, a corresponding label that can 
     * be used for output display purposes is also returned.
     * The Map returned contains the property names and property
     * labels as key-value pairs. This information can be used to display 
     * more readable output containing property labels and not property
     * names.
     *
     * @param type Either QUEUE or TOPIC.
     * @return Map containing attribute value pairs of property names
     *		and their display labels.
     */
    public Map getDisplayedDestinationObjectProperties(int type)
						throws JMSException  {
	Properties	ret;
	String		propName, propLabel;

	if (tmpDestination == null)  {
	    tmpDestination = (AdministeredObject)JMSObjFactory.createTopic(new Properties());
	}

	ret = new Properties();

	propName = DestinationConfiguration.imqDestinationName;
	propLabel = tmpDestination.getPropertyLabel(propName);
	ret.setProperty(propName, propLabel);

	return (ret);
    }

    /**
     * Returns a map of all the properties that a connection factory
     * object has. 
     * <P>
     * For each property name returned, a corresponding label that can 
     * be used for output display purposes is also returned.
     * The Map returned contains the property names and property
     * labels as key-value pairs. This information can be used to display 
     * more readable output containing property labels and not property
     * names.
     *
     * @param type Either QUEUE or TOPIC.
     * @return Map containing attribute value pairs of property names
     *		and their display labels.
     */
    public Map getAllConnectionFactoryObjectProperies(int type)
						throws JMSException  {
	Properties	ret;

	if (tmpConnFactory == null)  {
	    tmpConnFactory = 
	      (AdministeredObject)JMSObjFactory.createTopicConnectionFactory(new Properties());
	}

	ret = new Properties();

	for (Enumeration e = tmpConnFactory.enumeratePropertyNames(); e.hasMoreElements();) {
	    String propName = (String)e.nextElement();
	    String propLabel = tmpConnFactory.getPropertyLabel(propName);
	    ret.setProperty(propName, propLabel);
	}

	return (ret);
    }

    /**
     * Returns a subset of all the properties that a connection factory 
     * object has. 
     * <P>
     * This collection of properties can be used to construct a user
     * interface where not all of the object's properties will be 
     * displayed, and only a selected few (more important) ones are.
     * <P>
     * For each property name returned, a corresponding label that can 
     * be used for output display purposes is also returned.
     * The Map returned contains the property names and property
     * labels as key-value pairs. This information can be used to display 
     * more readable output containing property labels and not property
     * names.
     *
     * @param type Either QUEUE or TOPIC.
     * @return Map containing attribute value pairs of property names
     *		and their display labels.
     */
    public Map getDisplayedConnectionFactoryObjectProperies(int type)
						throws JMSException  {
	Properties	ret;
	String		propName, propLabel;

	if (tmpConnFactory == null)  {
	    tmpConnFactory = 
	      (AdministeredObject)JMSObjFactory.createTopicConnectionFactory(new Properties());
	}

	ret = new Properties();

	propName = ConnectionConfiguration.imqBrokerHostName;
	propLabel = tmpConnFactory.getPropertyLabel(propName);
	ret.setProperty(propName, propLabel);

	propName = ConnectionConfiguration.imqBrokerHostPort;
	propLabel = tmpConnFactory.getPropertyLabel(propName);
	ret.setProperty(propName, propLabel);

	propName = ConnectionConfiguration.imqConfiguredClientID;
	propLabel = tmpConnFactory.getPropertyLabel(propName);
	ret.setProperty(propName, propLabel);

	return (ret);
    }



    /**
     * Connect to the provider.
     * @exception JMSException thrown if connection to the provider
     * cannot be established or if an error occurs
     */
    public void connectToProvider() throws JMSException {
        connection = qcf.createQueueConnection(adminuserName, adminPassword);
        connection.setExceptionListener(this);
        connection.start();
        session = connection.createQueueSession
            (false, Session.CLIENT_ACKNOWLEDGE);
        requestQueue = session.createQueue(MessageType.JMQ_ADMIN_DEST);
        replyQueue = session.createTemporaryQueue();
        sender = session.createSender(requestQueue);
        sender.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
        receiver = session.createReceiver(replyQueue);

	hello();
    }

    /**
     * Disconnect from the provider.
     * @exception JMSException thrown if connection to the provider cannot
     * be closed or ir an error occurs
     */
    public void disconnectFromProvider() throws JMSException {
        sender.close();
        receiver.close();
        session.close();

	try  {
            connection.close();
	} catch (JMSException jmse)  {
	    if (!secureTransportUsed)  {
		throw (jmse);
	    }
	}
    }

    /**
     * Create a physical Destination within the JMS Provider using the provided
     * properties to define provider specific attributes.
     * Destination is not automatically bound into JNDI namespace.
     *
     * @param destinationName
     * @param destinationType QUEUE or TOPIC
     * @param properties creation properties.
     * @return Identifier for newly created Destination.
     * @exception JMSException thrown if Queue could not be created.
     */
    public void createProviderDestination(String destinationName,
                                          int destinationType,
                                          java.util.Map properties)
        throws JMSException {

        ObjectMessage requestMesg = null;
        Message replyMesg = null;

        // Create DestinationInfo.
        DestinationInfo di = new DestinationInfo();
        di.setName(destinationName);

	// REVISIT:
	// We only check for queueDeliveryPolicy in the properties
	// hard-coded string; left here because we are not sure
	// if we are going to expose this attribute yet
	String qPolicy = null;

	/*
	 * Early initialization to be used in error messages
	 */
	int typeMask = this.getDestTypeMask(destinationType, null);

	if (properties != null)  {
	    if (properties.containsKey("queueDeliveryPolicy")) {
	        qPolicy = (String)properties.get("queueDeliveryPolicy");
	        typeMask = this.getDestTypeMask(destinationType, qPolicy);
	    }

	    if (properties.containsKey(
		BrokerCmdOptions.PROP_NAME_MAX_ACTIVE_CONSUMER_COUNT)) {
		Object tmp = null;
		String val = null;
		tmp = properties.get(
		    BrokerCmdOptions.PROP_NAME_MAX_ACTIVE_CONSUMER_COUNT);

		if (tmp != null)  {
		    if (!(tmp instanceof String))  {
		        JMSException jmse;
		        jmse = new JMSException(
		            ar.getString(ar.E_SPI_DEST_CREATION_FAILED, 
				DestType.toString(typeMask),
		                destinationName)
			    + "\n"
			    + ar.getString(ar.E_SPI_ATTR_TYPE_NOT_STRING, 
		                BrokerCmdOptions.PROP_NAME_MAX_ACTIVE_CONSUMER_COUNT)
			    );

		        throw jmse;
		    }

		    val = (String)tmp;

	    	    try  {
		        di.setMaxActiveConsumers(Integer.parseInt(val));
		    } catch (Exception e)  {
		        JMSException jmse;
		        jmse = new JMSException(
		            ar.getString(ar.E_SPI_DEST_CREATION_FAILED, 
				DestType.toString(typeMask),
		                destinationName)
			    + "\n"
			    + ar.getString(ar.E_INVALID_INTEGER_VALUE, 
		                val, 
		                BrokerCmdOptions.PROP_NAME_MAX_ACTIVE_CONSUMER_COUNT));
                        throw (jmse);
                    }
		}
	    }
	}

	di.setType(typeMask);

        requestMesg = session.createObjectMessage();
        requestMesg.setJMSReplyTo(replyQueue);
        requestMesg.setIntProperty
            (MessageType.JMQ_MESSAGE_TYPE, MessageType.CREATE_DESTINATION);
        requestMesg.setObject(di);
        sender.send(requestMesg);

        replyMesg = receiver.receive(timeout);
        replyMesg.acknowledge();

        checkReplyTypeStatus(replyMesg,
                             MessageType.CREATE_DESTINATION_REPLY,
                             "CREATE_DESTINATION_REPLY");
    }

    /**
     * Delete a physical destination within the JMS Provider.
     * @param type        Either QUEUE or TOPIC.
     * @param destinationName
     * @exception JMSException thrown if Queue could not be deleted.
     */
    public void deleteProviderDestination(String destinationName,
                                          int type)
        throws JMSException {
        ObjectMessage requestMesg = null;
        Message replyMesg = null;

        requestMesg = session.createObjectMessage();
        requestMesg.setJMSReplyTo(replyQueue);
        requestMesg.setIntProperty
            (MessageType.JMQ_MESSAGE_TYPE, MessageType.DESTROY_DESTINATION);
        requestMesg.setStringProperty(MessageType.JMQ_DESTINATION, 
				      destinationName);
        requestMesg.setIntProperty(MessageType.JMQ_DEST_TYPE, 
				   this.getDestTypeMask(type, null));
        sender.send(requestMesg);

        replyMesg = receiver.receive(timeout);
        replyMesg.acknowledge();

        checkReplyTypeStatus(replyMesg,
            MessageType.DESTROY_DESTINATION_REPLY,
            "DESTROY_DESTINATION_REPLY");
    }

    /**
     * Get all provider destinations.
     * 
     * @return A multi dimensional array containing information
     *         about the JMS destinations. array[0] is a String[]
     *         listing the destination names. array[1] is a
     *         String[] listing the destination types.
     * @exception JMSException thrown if array could not be obtained.
     */
    public String[][] getProviderDestinations() throws JMSException {
        ObjectMessage mesg = null;

        mesg = session.createObjectMessage();
        mesg.setJMSReplyTo(replyQueue);
        mesg.setIntProperty
            (MessageType.JMQ_MESSAGE_TYPE, MessageType.GET_DESTINATIONS);
        sender.send(mesg);

        mesg = (ObjectMessage)receiver.receive(timeout);
        mesg.acknowledge();
        checkReplyTypeStatus(mesg, MessageType.GET_DESTINATIONS_REPLY,
            "GET_DESTINATIONS_REPLY");

	Object obj;
        if ((obj = mesg.getObject()) != null) {
            if (obj instanceof Vector)  {
        	Vector dests = (Vector)obj;

		/*
		 * Remove temporary, internal and admin destinations
		 * by creating a new Vector and adding all the valid 
		 * destinations into this new Vector.
		 */
		Vector validDests = new Vector();
	
		for (int i = 0; i < dests.size(); i++) {
		    DestinationInfo di = (DestinationInfo)dests.elementAt(i);
		    if ((!MessageType.JMQ_ADMIN_DEST.equals(di.name)) && 
            (!MessageType.JMQ_BRIDGE_ADMIN_DEST.equals(di.name)) &&
			(!DestType.isInternal(di.fulltype)) &&
			(!DestType.isTemporary(di.type))) {
			validDests.add(di);
		    }
		}

	        int numElements = validDests.size();
	        String[][] destinations = new String[2][numElements];

	        for (int i = 0; i < numElements; i++) {
	    	    DestinationInfo di = (DestinationInfo)validDests.get(i);
		    destinations[0][i] = di.name;
		    destinations[1][i] = this.getDestinationType(di.type);
	        }
	        return destinations;
            }
        }
        return null;
    }

    /**
     * Start the provider.
     *
     * @param iMQHome Location of the broker executable.
     * @param optArgs Array of optional broker command line arguments.
     * @param serverName Instance name of the server.
     * @exception IOException thrown if the server startup fails.
     */
    public void startProvider(String iMQHome, String optArgs[],
        String serverName) throws IOException, JMSException {
        String iMQBrokerPath;
        String append = null;

        if (java.io.File.separator.equals("\\")) {
            // Windows path
            //    <iMQHome>\\imqbrokersvc.exe -console
            iMQBrokerPath  = iMQHome +
                java.io.File.separator + "imqbrokersvc.exe";
            append = "-console";
        }
        else {
            // Unix path.
            //    <iMQHome>/bin/imqbrokerd
            iMQBrokerPath  = iMQHome +
                java.io.File.separator + "imqbrokerd";
        }

        String portStr =
            qcf.getProperty(ConnectionConfiguration.imqBrokerHostPort);

        // Important : Do not change the command line parameter order
        // unless you know what you are doing. Here are the
        // constraints -
        //
        // 1. The "-javahome" argument, if present, must be first.
        //    Otherwise it does not work on Windows. The caller (of
        //    this method) is responsible for making sure that the
        //    "-javahome" argument is first in the 'optArgs' string.
        //
        // 2. The -name, -port and -silent arguments must be at
        //    placed at the end, so that they override anything
        //    specified in the optArgs...
        //
        // 3. The -bgnd argument is required for proper signal
        //    handling on solaris.
        //

        Vector v = new Vector();
        v.add(iMQBrokerPath);

        if (optArgs != null) {
            for (int i = 0; i < optArgs.length; i++)
                v.add(optArgs[i]);
        }

        if (serverName != null) {
            v.add("-name");
            v.add(serverName);
        }

        if (append != null)
            v.add(append);

        v.add("-port");
        v.add(portStr);
        // CR 6944162 Disable use of -bgnd (used on Solaris) 
        // because it prevents passwords being passed to the broker via standard input
        //v.add("-bgnd");
        
        // disable use of -errorwarn if FINE logging is enabled
        if (!logger.isLoggable(Level.FINE)){
        	// normally we only want to see errors in stdout
        	v.add("-ttyerrors");
        }
        v.add("-managed");
        v.add("-read-stdin");

        String[] cmdLine = (String[]) v.toArray(new String[0]);
        
        // Log the command line we are about to execute
        if (logger.isLoggable(Level.FINE)){
        	String commandLineToLog = "";
        	for (int i = 0; i < cmdLine.length; i++) {
        		commandLineToLog = commandLineToLog + cmdLine[i] + " ";
			}
        	logger.fine("MQJMSRA: Starting LOCAL broker with command line: "+commandLineToLog);
        }

        launchAndWatch(cmdLine);
             
        }

	private void launchAndWatch(String[] cmdLine) throws IOException {
        Process p = Runtime.getRuntime().exec(cmdLine);

        BrokerWatcher brokerWatcher = new BrokerWatcher(p,cmdLine);
        brokerWatcher.setDaemon(true);
        brokerWatcher.start();
        
        
        // start a thread to log any error messages from the new broker as a WARN message
        boolean isError=true;
        StreamGobbler errorGobbler = new 
            StreamGobbler(p.getErrorStream(), isError,"Local broker: ");          
        errorGobbler.setDaemon(true);
        errorGobbler.start();
        
        // start a thread to log any output messages from the new broker as an INFO message
        isError=false;
        StreamGobbler outputGobbler = new 
            StreamGobbler(p.getInputStream(), isError,"Local broker: ");
        outputGobbler.setDaemon(true);
        outputGobbler.start();
        
		// generate the broker properties afresh
		Properties brokerProps = brokerPropertiesHolder.getProperties();
        
        // write properties to the input stream of the new process  
		try {
			OutputStreamWriter osw = new OutputStreamWriter(p.getOutputStream());
			BufferedWriter bw = new BufferedWriter(osw);
			for (Enumeration e = brokerProps.propertyNames(); e.hasMoreElements();) {
				String thisPropertyName = (String) e.nextElement();
				String thisPropertyValue = brokerProps.getProperty(thisPropertyName);
				bw.write(thisPropertyName + "=" + thisPropertyValue);
				bw.newLine();
			}
			if (this.adminPassword!=null){
				// if set, override any admin password set via brokerProps
				bw.write("imq.imqcmd.password="+this.adminPassword);
				bw.newLine();
			}
			bw.close();
		} catch (IOException ioe) {
			ioe.printStackTrace();
		}
    }

    /**
     * Ping the provider.
     * @exception JMSException thrown if ping fails
     */
	public void pingProvider() throws JMSException {

		// temporarily turn off logging because we do not want to log a message if the provider is not running
		// as this might not be an error.
		Logger rootLogger = Logger.getLogger(ConnectionImpl.ROOT_LOGGER_NAME);
		Level savedLevel = rootLogger.getLevel();
		rootLogger.setLevel(Level.SEVERE);

		Connection pingConn = null;
		try {
			pingConn = qcf.createQueueConnection(adminuserName, adminPassword);
		} finally {
			// re-enable logging logging
			rootLogger.setLevel(savedLevel);
		}

		pingConn.setExceptionListener(this);
		pingConn.start();
		try {
			pingConn.close();
		} catch (JMSException jmse) {
			if (!secureTransportUsed) {
				throw (jmse);
			}
		}
	}

    /**
     * Ping the provider using the specified parameters.
     *
     * @param username Administrator username
     * @param password Administrator password
     * @param port MQ broker port
     *
     * @exception JMSException thrown if ping fails
     */
    public void pingProvider(String username, String password, int port) 
			throws JMSException {
        QueueConnectionFactory		pingQcf;
        Connection			pingConn;
	Properties			props;

	/*
	 * Get configuration used to create 'real' admin connections.
	 */
	props = qcf.getConfiguration();

	/*
	 * Create new connection factory and apply the above configuration
	 * to it.
	 */
        pingQcf = new QueueConnectionFactory();
        pingQcf.setConnectionType(ClientConstants.CONNECTIONTYPE_ADMIN);

        for (Enumeration e = props.propertyNames();
                         e.hasMoreElements();) {
            String propName = (String)e.nextElement();
            String value  = props.getProperty(propName);

            if (value != null)  {
                pingQcf.setProperty(propName, value);
            }
        }

	/*
	 * Use specified port in his new connection factory.
	 */
	pingQcf.setProperty(ConnectionConfiguration.imqBrokerHostPort,
			String.valueOf(port));

	/*
	 * Create new connection object using specified username/password.
	 */
        pingConn = pingQcf.createQueueConnection(username, password);

	/*
	 * Perform ping operation.
	 */
        pingConn.setExceptionListener(this);
        pingConn.start();
	try  {
            pingConn.close();
	} catch (JMSException jmse)  {
	    if (!secureTransportUsed)  {
		throw (jmse);
	    }
	}
    }

    /**
     * Get the property value from a provider
     * @exception JMSException thrown if the get fails.
     */
    private String getProviderPropertyValue(String propName) throws JMSException {
        ObjectMessage requestMesg = null;
        ObjectMessage replyMesg = null;
	String	propVal = null;

        requestMesg = session.createObjectMessage();
        requestMesg.setJMSReplyTo(replyQueue);
	requestMesg.setIntProperty
	    (MessageType.JMQ_MESSAGE_TYPE, MessageType.GET_BROKER_PROPS);

	sender.send(requestMesg);

	replyMesg = (ObjectMessage)receiver.receive(timeout);
        replyMesg.acknowledge();

        checkReplyTypeStatus(replyMesg,
                             MessageType.GET_BROKER_PROPS_REPLY,
                             "GET_BROKER_PROPS_REPLY");

        Object obj;
		     
	if ((obj = replyMesg.getObject()) != null) {
	    if (obj instanceof Properties)  {
		Properties	props = (Properties)obj;
		propVal = props.getProperty(propName);
	    }
	}

	return (propVal);
    }

    /**
     * Get the instance name of the provider
     * @exception JMSException thrown if the get fails.
     */
    public String getProviderInstanceName() throws JMSException {
        return(getProviderPropertyValue(
		BrokerConstants.PROP_NAME_BKR_INSTANCE_NAME));
    }

    /**
     * Get the VARHOME directory of the provider
     * @exception JMSException thrown if the get fails.
     */
    public String getProviderVarHome() throws JMSException {
        return(getProviderPropertyValue(
		BrokerConstants.PROP_NAME_BKR_VARHOME));
    }


    /**
     * Shutdown the provider.
     * @exception JMSException thrown if the shutdown fails
     * @throws  
     */
    public void shutdownProvider() throws JMSException {        
        try {
        	tryToShutdownProvider();
		} catch (JMSException jmse) {
			if ((com.sun.messaging.jmq.jmsclient.resources.ClientResources.X_CONSUMER_CLOSED.equals(jmse.getErrorCode())) |
                (com.sun.messaging.jmq.jmsclient.resources.ClientResources.X_SESSION_CLOSED.equals(jmse.getErrorCode()))) {
				// we seem to have lost the connection - try once to reconnect before giving up
			    try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {}
			    connectToProvider();
			    tryToShutdownProvider();
			} else {
				// re-throw the exception if not X_CONSUMER_CLOSED or X_SESSION_CLOSED 
				throw jmse;
			}		
		}
    }
    
	private void tryToShutdownProvider() throws JMSException {
		ObjectMessage requestMesg;
		Message replyMesg;
		requestMesg = session.createObjectMessage();
		requestMesg.setJMSReplyTo(replyQueue);
		requestMesg.setIntProperty(MessageType.JMQ_MESSAGE_TYPE,MessageType.SHUTDOWN);
		sender.send(requestMesg);
		
		// disable reconnect since we don't want the client to try to reconnect
		//ConnectionImpl ci = (ConnectionImpl)connection;
		//ci.setImqReconnect(false);
		
		replyMesg = receiver.receive(timeout);
		checkReplyTypeStatus(replyMesg, MessageType.SHUTDOWN_REPLY,"SHUTDOWN_REPLY");
	}

    /**
	 * Restart the provider.
	 * 
	 * @exception JMSException
	 *                thrown if the restart fails
	 */
    public void restartProvider() throws JMSException {
        ObjectMessage requestMesg = null;
        Message replyMesg = null;

        requestMesg = session.createObjectMessage();
        requestMesg.setJMSReplyTo(replyQueue);
        requestMesg.setIntProperty
            (MessageType.JMQ_MESSAGE_TYPE, MessageType.SHUTDOWN);
        requestMesg.setBooleanProperty(MessageType.JMQ_RESTART, true);
        sender.send(requestMesg);

        replyMesg = receiver.receive(timeout);
        checkReplyTypeStatus(replyMesg, MessageType.SHUTDOWN_REPLY,
             "SHUTDOWN_REPLY");
    }

    /**
     * Return the provider host name
     * @exception JMSException
     */
    public String getProviderHostName() throws JMSException {
        return qcf.getProperty(ConnectionConfiguration.imqBrokerHostName);
    }

    /**
     * Return the provider host port number
     * @exception JMSException
     */
    public String getProviderHostPort() throws JMSException {
        return qcf.getProperty(ConnectionConfiguration.imqBrokerHostPort);
    }

    /**
     * Delete provider instance.
     *
     * @param mqBinDir Location of MQ's bin directory, which contains
     * the broker executable.
     * @param optArgs Optional broker command line arguments.
     * @param serverName instance name of MQ broker to delete
     * @exception JMSException thrown if the delete fails.
     */
    public void deleteProviderInstance(String mqBinDir, 
				String optArgs,
				String serverName) throws IOException, JMSException  {
        String iMQBrokerPath;
	int exitCode = 0;
	boolean interrupted = false;

        if (java.io.File.separator.equals("\\")) {
            // Windows path
            //    <mqBinDir>\\bin\\imqbrokerd.exe
            iMQBrokerPath  = mqBinDir +
                java.io.File.separator + "imqbrokerd.exe";
        }
        else {
            // Unix path.
            //    <mqBinDir>/bin/imqbrokerd
            iMQBrokerPath  = mqBinDir +
                java.io.File.separator + "imqbrokerd";
        }

        String portStr =
            qcf.getProperty(ConnectionConfiguration.imqBrokerHostPort);

        // Important : Do not change the command line parameter order
        // unless you know what you are doing. Here are the
        // constraints -
        //
        // 1. The "-javahome" argument, if present, must be first.
        //    Otherwise it does not work on Windoze. The caller (of
        //    this method) is responsible for making sure that the
        //    "-javahome" argument is first in the 'optArgs' string.
        //
        // 2. The -name, -port and -silent arguments must be at
        //    placed at the end, so that they override anything
        //    specified in the optArgs...
        //
        // 3. The -bgnd argument is required for proper signal
        //    handling on solaris.
        //

        String cmdLine = iMQBrokerPath;
        if (optArgs != null)
            cmdLine = cmdLine + " " + optArgs;
        if (serverName != null)
            cmdLine = cmdLine + " -name " + serverName;
        cmdLine = cmdLine + " -remove instance -silent -force";

        Process p = Runtime.getRuntime().exec(cmdLine);

        // Close the receiver end of stdout and stderr streams.
        // Otherwise the child process blocks while trying to
        // write...
        p.getInputStream().close();
        p.getErrorStream().close();

	try  {
	    p.waitFor();
	    exitCode = p.exitValue();
	} catch (InterruptedException iex)  {
	    interrupted = true;
	}

	if (interrupted)  {
	    throw new javax.jms.JMSException(ar.getKString(ar.X_JMSSPI_DELETE_INST_INT_PREM,
				serverName));
	}
	
	/*
	 * Proper handling of exit codes will be done once the following
	 * bug is fixed:
	 * 4713518 - imqbrokerd -remove: Enhancements needed for app server 
	 *		integration
	 */
	switch (exitCode)  {
	case BrokerExitCode.NORMAL:
	break;

	case BrokerExitCode.INSTANCE_NOT_EXISTS:
	    throw new javax.jms.JMSException(ar.getKString(ar.X_JMSSPI_DELETE_INST_NOT_EXIST,
				serverName));

	case BrokerExitCode.INSTANCE_BEING_USED:
	    throw new javax.jms.JMSException(ar.getKString(ar.X_JMSSPI_DELETE_INST_BEING_USED,
				serverName));

	case BrokerExitCode.NO_PERMISSION_ON_INSTANCE:
	    throw new javax.jms.JMSException(ar.getKString(ar.X_JMSSPI_DELETE_INST_NO_PERM,
				serverName));

	case BrokerExitCode.PROBLEM_REMOVING_PERSISTENT_STORE:
	    throw new javax.jms.JMSException(
		ar.getKString(ar.X_JMSSPI_DELETE_INST_PROB_RM_STORE, serverName));

	case BrokerExitCode.IOEXCEPTION:
	    throw new javax.jms.JMSException(
		ar.getKString(ar.X_JMSSPI_DELETE_INST_IOEXCEPTION, serverName));

	default:
	    throw new javax.jms.JMSException(
		ar.getKString(ar.X_JMSSPI_DELETE_INST_UNKNOWN, serverName));
	}
    }

    /**
     * Delete provider instance.
     *
     * @param mqBinDir Location of MQ's bin directory, which contains
     * the broker executable.
     * @param optArgs Array of optional broker command line arguments.
     * @param serverName instance name of MQ broker to delete
     * @exception JMSException thrown if the delete fails.
     */
    public void deleteProviderInstance(String mqBinDir, 
				String optArgs[],
				String serverName) throws IOException, JMSException  {
        String iMQBrokerPath;
	int exitCode = 0;
	boolean interrupted = false;

        if (java.io.File.separator.equals("\\")) {
            // Windows path
            //    <mqBinDir>\\bin\\imqbrokerd.exe
            iMQBrokerPath  = mqBinDir +
                java.io.File.separator + "imqbrokerd.exe";
        }
        else {
            // Unix path.
            //    <mqBinDir>/bin/imqbrokerd
            iMQBrokerPath  = mqBinDir +
                java.io.File.separator + "imqbrokerd";
        }

        String portStr =
            qcf.getProperty(ConnectionConfiguration.imqBrokerHostPort);

        // Important : Do not change the command line parameter order
        // unless you know what you are doing. Here are the
        // constraints -
        //
        // 1. The "-javahome" argument, if present, must be first.
        //    Otherwise it does not work on Windoze. The caller (of
        //    this method) is responsible for making sure that the
        //    "-javahome" argument is first in the 'optArgs' string.
        //
        // 2. The -name, -port and -silent arguments must be at
        //    placed at the end, so that they override anything
        //    specified in the optArgs...
        //
        // 3. The -bgnd argument is required for proper signal
        //    handling on solaris.
        //

        Vector v = new Vector();
        v.add(iMQBrokerPath);

        if (optArgs != null) {
            for (int i = 0; i < optArgs.length; i++)
                v.add(optArgs[i]);
        }

        if (serverName != null) {
            v.add("-name");
            v.add(serverName);
        }

        v.add("-remove");
        v.add("instance");
        v.add("-silent");
        v.add("-force");

        String[] cmdLine = (String[]) v.toArray(new String[0]);
        Process p = Runtime.getRuntime().exec(cmdLine);

        // Close the receiver end of stdout and stderr streams.
        // Otherwise the child process blocks while trying to
        // write...
        p.getInputStream().close();
        p.getErrorStream().close();

	try  {
	    p.waitFor();
	    exitCode = p.exitValue();
	} catch (InterruptedException iex)  {
	    interrupted = true;
	}

	if (interrupted)  {
	    throw new javax.jms.JMSException(ar.getKString(ar.X_JMSSPI_DELETE_INST_INT_PREM,
				serverName));
	}
	
	/*
	 * Proper handling of exit codes will be done once the following
	 * bug is fixed:
	 * 4713518 - imqbrokerd -remove: Enhancements needed for app server 
	 *		integration
	 */
	switch (exitCode)  {
	case BrokerExitCode.NORMAL:
	break;

	case BrokerExitCode.INSTANCE_NOT_EXISTS:
	    throw new javax.jms.JMSException(ar.getKString(ar.X_JMSSPI_DELETE_INST_NOT_EXIST,
				serverName));

	case BrokerExitCode.INSTANCE_BEING_USED:
	    throw new javax.jms.JMSException(ar.getKString(ar.X_JMSSPI_DELETE_INST_BEING_USED,
				serverName));

	case BrokerExitCode.NO_PERMISSION_ON_INSTANCE:
	    throw new javax.jms.JMSException(ar.getKString(ar.X_JMSSPI_DELETE_INST_NO_PERM,
				serverName));

	case BrokerExitCode.PROBLEM_REMOVING_PERSISTENT_STORE:
	    throw new javax.jms.JMSException(
		ar.getKString(ar.X_JMSSPI_DELETE_INST_PROB_RM_STORE, serverName));

	case BrokerExitCode.IOEXCEPTION:
	    throw new javax.jms.JMSException(
		ar.getKString(ar.X_JMSSPI_DELETE_INST_IOEXCEPTION, serverName));

	default:
	    throw new javax.jms.JMSException(
		ar.getKString(ar.X_JMSSPI_DELETE_INST_UNKNOWN, serverName));
	}

    }

    private void hello() throws JMSException {
        ObjectMessage requestMesg = null;
        Message replyMesg = null;

        requestMesg = session.createObjectMessage();
        requestMesg.setJMSReplyTo(replyQueue);
        requestMesg.setIntProperty
            (MessageType.JMQ_MESSAGE_TYPE, MessageType.HELLO);
        sender.send(requestMesg);

        replyMesg = receiver.receive(timeout);
        replyMesg.acknowledge();
        checkReplyTypeStatus
            (replyMesg, MessageType.HELLO_REPLY, "HELLO_REPLY");
    }

    /*
     * Map is an interface and its implementation can be anything that
     * implements the Map interface.  Whatever it is, convert it to a 
     * Properties object.
     */
    private Properties getProperties(java.util.Map properties) {

        Properties tmpProps = null;

        if (properties == null)
            tmpProps = new Properties();

        else if (properties instanceof Properties) {
            tmpProps = (Properties)properties;

        } else {
            tmpProps = new Properties();
            Iterator iterator = properties.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry entry = (Map.Entry)iterator.next();
                tmpProps.put(entry.getKey(), entry.getValue());
            }
        }
        return tmpProps;
    }

    /*
     * Set the properties on this object.
     */
    private static void setProperties(AdministeredObject obj,
                                Properties objProps)
                                throws JMSException {
        /*
         * Set the specified properties on the new object.
         */
        for (Enumeration e = objProps.propertyNames(); e.hasMoreElements();) {

            String propName = (String)e.nextElement();
            String value  = objProps.getProperty(propName);
            if (value != null)
                obj.setProperty(propName, value);
        }
    }

    private void createFactory(Properties connectionProps)
        throws JMSException {

        qcf = new QueueConnectionFactory();
        qcf.setConnectionType(ClientConstants.CONNECTIONTYPE_ADMIN);

        for (Enumeration e = connectionProps.propertyNames();
                         e.hasMoreElements();) {
            String propName = (String)e.nextElement();
            String value  = connectionProps.getProperty(propName);

            if (value != null)  {
                qcf.setProperty(propName, value);
            }
        }
    }

	private void fillDefaultConnectionFactoryProperties(Properties props)
		throws JMSException {

		String val;
		if (props.getProperty(ConnectionConfiguration.imqBrokerHostName) == null &&
			(val=qcf.getProperty(ConnectionConfiguration.imqBrokerHostName)) != null) {
			props.setProperty(ConnectionConfiguration.imqBrokerHostName, val);			
		}
		if (props.getProperty(ConnectionConfiguration.imqBrokerHostPort) == null &&
			(val=qcf.getProperty(ConnectionConfiguration.imqBrokerHostPort)) != null) {
			props.setProperty(ConnectionConfiguration.imqBrokerHostPort, val);			
		}
	}

    // REVISIT: 
    // hard-coded string
    private static int getDestTypeMask(int type, String policy) {
        int mask = -1;

	if (type == TOPIC)
	    mask = DestType.DEST_TYPE_TOPIC;
	else if (type == QUEUE)
	    mask = DestType.DEST_TYPE_QUEUE;

	if (policy == null)
	    return mask;

	if (type == QUEUE) {
            if (policy.equals("s")) {
                mask |= DestType.DEST_FLAVOR_SINGLE;
            } else if (policy.equals("f")) {
                mask |= DestType.DEST_FLAVOR_FAILOVER;
            } else if (policy.equals("r")) {
                mask |= DestType.DEST_FLAVOR_RROBIN;
	    }
	}
	return mask;
    }

    private static String getDestinationType(int mask) {
        if (DestType.isTopic(mask))
            return String.valueOf(TOPIC);
        else if (DestType.isQueue(mask))
            return String.valueOf(QUEUE);
        else
            return String.valueOf(UNKNOWN);
    }

    private void checkReplyTypeStatus(Message mesg, 
			              int msgType, 
			              String msgTypeString) 
	throws JMSException {

        int actualMsgType = -1,
            actualReplyStatus = -1;

        /* There is a timing problem in the protocol.  
           The GOODBYE message could be processed before the SHUTDOWN_REPLY
           message and therefore could be sending null as a value for 'mesg'
           when receive() returns.  We will assume that the SHUTDOWN operation
           was successful when we receive status == 200 or mesg == null.
         */
        if (mesg == null)  {
            if (msgType == MessageType.SHUTDOWN_REPLY) {
                return;
            }
        }

        actualMsgType = mesg.getIntProperty(MessageType.JMQ_MESSAGE_TYPE);
        actualReplyStatus = mesg.getIntProperty(MessageType.JMQ_STATUS);

        if ((msgType == actualMsgType) &&
            (actualReplyStatus == MessageType.OK))
            return;

        /*
         * Otherwise, report an error
         */
	String error = null;
	try {
            error = mesg.getStringProperty(MessageType.JMQ_ERROR_STRING);
	} catch (Exception e) {
	}	
	throw new JMSException(error);
    }

    /*
     * BEGIN INTERFACE ExceptionListener
     */
    public void onException(JMSException jmse) {
    }
    /*
     * END INTERFACE ExceptionListener
     */
    
    class BrokerWatcher extends Thread {
    	Process p;
    	String[] cmdLine;

		BrokerWatcher(Process p, String[] cmdLine) {
			this.p=p;
			this.cmdLine=cmdLine;
		}

		public void run() {
			logger.fine("BrokerWatcher started");
			try {
				int status = p.waitFor();
				logger.fine("BrokerWatcher: Broker returned with status="+status);
				if (status==255){
					// restart broker
					launchAndWatch(cmdLine);
					logger.fine("BrokerWatcher terminating after restarting broker");
				}
				logger.fine("BrokerWatcher terminating after normal exit");
			} catch (InterruptedException e) {
				logger.warning("InterruptedException watching broker: "+e.getMessage());
			} catch (IOException e) {
				logger.warning("Error restarting broker: "+e.getMessage());
			}

		}
    }
    
    class StreamGobbler extends Thread {
		InputStream is;
		boolean isError;
		String prefix;

		/**
		 * 
		 * @param is
		 * @param isError If true, log messages as a WARN message. Otherwise log messages as an INFO message
		 * @param prefix
		 */
		StreamGobbler(InputStream is, boolean isError, String prefix) {
			this.is = is;
			this.isError = isError;
			this.prefix = prefix;
		}

		public void run() {
			try {
				InputStreamReader isr = new InputStreamReader(is);
				BufferedReader br = new BufferedReader(isr);
				String line = null;
				String message = prefix;
				int noOfLines=0;
				while ((line = br.readLine()) != null) {
					noOfLines++;
					if (noOfLines>1) {
						message = message + "\n";
					}
					message = message  + line;
				}

				if (noOfLines>0) {
					if (isError) {
						logger.warning(message);
					} else {
						logger.info(message);
					}
				}
			} catch (IOException ioe) {
				ioe.printStackTrace();
			}
		}
    }

}
