Package org.apache.geronimo.console.cli

Source Code of org.apache.geronimo.console.cli.DConfigBeanConfigurator

/**
*
* Copyright 2003-2004 The Apache Software Foundation
*
*  Licensed 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.geronimo.console.cli;

import java.beans.*;
import java.io.*;
import java.util.*;
import java.lang.reflect.InvocationTargetException;
import javax.enterprise.deploy.spi.DConfigBean;
import javax.enterprise.deploy.spi.DConfigBeanRoot;
import javax.enterprise.deploy.spi.exceptions.ConfigurationException;
import javax.enterprise.deploy.model.DDBean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.geronimo.common.propertyeditor.PropertyEditors;

/**
* Knows how to configure a DConfigBean at the command line.  The editing process
* is a series of reads and writes to the provided input and output streams,
* which basically presents information and a prompt to the user, gathers their
* input, and repeats.  They can navigate through a tree of DConfigBeans and
* Java Beans, adding, removing, and editing properies on beans where
* appropriate.
* <p>
* Note: it might make sense to break this class up eventually.  Particularly if
* we want to allow the user to navigate between arbitrary DDBeans (standard DD)
* and their matching DConfigBeans (server-specific DD).  Right now they can only
* edit one tree at a time, either the whole DDBean tree, or the whole
* DConfigBean tree.
* </p>
* @version $Rev: 46019 $ $Date: 2004-09-14 04:56:06 -0500 (Tue, 14 Sep 2004) $
*/
public class DConfigBeanConfigurator {
    private final static Log log = LogFactory.getLog(DConfigBeanConfigurator.class);
    private PrintWriter out;
    private BufferedReader in;
    private Stack beans = new Stack();

    /**
     * Creates a new instance, based on the supplied config bean root and
     * input and output streams.
     */
    public DConfigBeanConfigurator(DConfigBeanRoot bean, PrintWriter out, BufferedReader in) {
        this.out = out;
        this.in = in;
        beans.push(bean);
    }

    /**
     * Begins the process of configuring the DConfigBean tree.  When this method
     * returns, the user has finished editing the DConfigBeans (or a fatal error
     * caused the editing to abort).
     *
     * @return <code>true</code> if the editing completed normally
     *        (<code>false</code> if there was a fatal error).
     */
    public boolean configure() {
        try {
            initialize();
            return true;
        } catch(IntrospectionException e) {
            log.error("Unable to introspect a JavaBean", e);
        } catch(IOException e) {
            log.error("Unable to gather input from user", e);
        } catch(InvocationTargetException e) {
            log.error("Unable to read or write a JavaBean property", e.getTargetException());
        } catch(IllegalAccessException e) {
            log.error("Unable to read or write a JavaBean property", e);
        } catch(ConfigurationException e) {
            log.error("Unable to generate a child DConfigBean", e);
        } catch(InstantiationException e) {
            log.error("Unable to generate a child bean", e);
        }
        return false;
    }

    /**
     * The main logic loop for the editing process.  This starts an endless loop,
     * where each iteration prints information on the current bean and prompts
     * the user for an action.  A stack is maintained of the current beans from
     * root (bottom of the stack) to the current leaf node (top of the stack).
     * The main options offered here are to move to a parent or child bean (if
     * available) or to edit a property on the current bean.
     */
    private void initialize() throws IntrospectionException, IOException, InvocationTargetException, IllegalAccessException, ConfigurationException, InstantiationException {
        boolean forward = true;
        BeanInfo info;
        PropertyDescriptor[] properties = new PropertyDescriptor[0];
        PropertyDescriptor[] readOnly = new PropertyDescriptor[0];
        PropertyDescriptor[] childProps = new PropertyDescriptor[0];
        Map childTypes = new HashMap();
        while(true) {
            out.println("\n\n");
            Object bean = beans.peek();
            int count;

            // Load top-level info
            info = Introspector.getBeanInfo(bean.getClass());
            String indent = printLocation();

            // Load children
            childTypes.clear();
            if(bean instanceof DConfigBean) {
                DConfigBean dcb = (DConfigBean) bean;
                String[] xpaths = dcb.getXpaths();
                for(int i=0; i<xpaths.length; i++) {
                    DDBean[] ddbs = dcb.getDDBean().getChildBean(xpaths[i]);
                    if(ddbs.length != 0) {
                        DConfigBean[] list = new DConfigBean[ddbs.length];
                        for(int j = 0; j < ddbs.length; j++) {
                            list[j] = dcb.getDConfigBean(ddbs[j]);
                        }
                        childTypes.put(Introspector.getBeanInfo(list[0].getClass()).getBeanDescriptor().getDisplayName(), list);
                    }
                }
                for(Iterator iterator = childTypes.keySet().iterator(); iterator.hasNext();) {
                    String s = (String)iterator.next();
                    int number = ((DConfigBean[])childTypes.get(s)).length;
                    out.println(indent+"+ "+s+" ("+number+" entr"+(number == 1 ? "y" : "ies")+")");
                }
            }
            childProps = getChildProperties(info.getPropertyDescriptors());
            for(int i = 0; i < childProps.length; i++) {
                PropertyDescriptor prop = childProps[i];
                if(prop instanceof IndexedPropertyDescriptor) {
                    int number = ((Object[])prop.getReadMethod().invoke(bean, new Object[0])).length;
                    out.println(indent+"+ "+prop.getDisplayName()+" ("+number+" entr"+(number == 1 ? "y" : "ies")+")");
                } else {
                    out.println(indent+"+ "+prop.getDisplayName()+" (child property)");
                }
            }
            out.println();

            // Load properties todo: handle properties of type bean but not DConfigBean and indexed properties
            count = 0;
            properties = getNormalProperties(info.getPropertyDescriptors());
            readOnly = getReadOnly(info.getPropertyDescriptors());
            for(int i = 0; i < readOnly.length; i++) {
                PropertyDescriptor property = readOnly[i];
                out.println(property.getDisplayName()+": "+property.getReadMethod().invoke(bean, new Object[0]));
            }
            if(properties.length > 0) {
                out.println("Properties for "+getFullName(bean)+":");
            }
            for(int i = 0; i < properties.length; i++) {
                PropertyDescriptor property = properties[i];
                out.println("  "+(++count)+": "+property.getDisplayName()+" ("+property.getReadMethod().invoke(bean, new Object[0])+")");
            }
            out.flush();

            // Auto-navigate
            if(properties.length == 0 && childTypes.size() == 1 && childProps.length == 0) {
                DConfigBean[] children = (DConfigBean[])childTypes.values().iterator().next();
                if(children.length == 1) {
                    if(forward) {
                        out.println("Nothing interesting to do here.  Moving on.");
                        beans.push(children[0]);
                        continue;
                    } else if(beans.size() > 1) {
                        out.println("Nothing interesting to do here.  Moving on.");
                        beans.pop();
                        continue;
                    }
                }
            } else if(properties.length == 0 && childTypes.size() == 0 && childProps.length == 1) {
                if(!(childProps[0] instanceof IndexedPropertyDescriptor)) {
                    if(forward) {
                        out.println("Nothing interesting to do here.  Moving on.");
                        beans.push(childProps[0].getReadMethod().invoke(bean, new Object[0]));
                        continue;
                    } else if(beans.size() > 1) {
                        out.println("Nothing interesting to do here.  Moving on.");
                        beans.pop();
                        continue;
                    }
                }
            }
            if(properties.length > 0) {
                out.println();
            }

            // Show navigation options
            out.print("Action (");
            boolean first = true;
            if(properties.length > 0) {
                if(!first) {out.print(" / ");}
                out.print("Edit [P]roperty");
                first = false;
            }
            if(childTypes.size() > 0 || childProps.length > 0) {
                if(!first) {out.print(" / ");}
                out.print("Move [D]own");
                first = false;
            }
            if(beans.size() > 1) {
                if(!first) {out.print(" / ");}
                out.print("Move [U]p");
                first = false;
            }
            if(!first) {out.print(" / ");}
            out.print("[Q]uit");
            first = false;
            out.print("): ");
            out.flush();
            String choice = in.readLine().trim().toLowerCase();
            if(choice.equals("u")) {
                forward = false;
                beans.pop();
                continue;
            } else if(choice.equals("d")) {
                forward = true;
                if(childTypes.size() == 0 && childProps.length == 0) {
                    log.warn("No children available here.");
                    continue;
                } else {
                    selectChildBean(childTypes, bean, childProps);
                    continue;
                }
            } else if(choice.equals("q")) {
                return;
            } else if(choice.equals("p")) {
                if(properties.length == 0) {
                    log.warn("No editable properties available here.");
                    continue;
                } else {
                    editProperty(bean, properties);
                    continue;
                }
            } else if(isNumber(choice)) {
                int value = Integer.parseInt(choice);
                if(value > 0 && value <= properties.length) {
                    editProperty(bean, properties[value-1]);
                    continue;
                }
            }
            log.error("I don't know how to do that (yet)");
        }
    }

    /**
     * The user wants to edit a property.  This method figures out which one (of
     * the properties available for the bean).
     */
    private void editProperty(Object bean, PropertyDescriptor[] properties) throws IOException, InvocationTargetException, IllegalAccessException {
        if(properties.length == 1) {
            editProperty(bean, properties[0]);
            return;
        }
        String choice = null;
        while(true) {
            out.print("Edit which property (1-"+properties.length+")? ");
            out.flush();
            choice = in.readLine();
            try {
                int value = Integer.parseInt(choice);
                if(value > 0 && value <= properties.length) {
                    editProperty(bean, properties[value-1]);
                    return;
                }
            } catch(NumberFormatException e) {}
        }
    }

    /**
     * Manages the editing of a single property.
     */
    private void editProperty(final Object bean, final PropertyDescriptor property) throws InvocationTargetException, IllegalAccessException, IOException {
        final PropertyEditor pe = PropertyEditors.findEditor(property.getPropertyType());
        pe.addPropertyChangeListener(new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                try {
                    property.getWriteMethod().invoke(bean, new Object[]{pe.getValue()});
                    pe.removePropertyChangeListener(this);
                } catch(IllegalAccessException e) {
                    log.error("Not allowed to set property", e);
                } catch(IllegalArgumentException e) {
                    log.error("Invalid value for property", e);
                } catch(InvocationTargetException e) {
                    log.error("Exception occured while setting property", e.getTargetException());
                }
            }
        });
        out.println("\nEditing Property "+property.getDisplayName());
        Object value = property.getReadMethod().invoke(bean, new Object[0]);
        if(value == null) {
            value = pe.getJavaInitializationString();
        }
        out.println("  Old value is: '"+value+"'");
        out.println("  Specify a new value.  Enter nothing to keep the current value.\n" +
                    "  Type (empty) for an empty string or (null) for a null.");
        out.print("New Value: ");
        out.flush();
        String choice = in.readLine();
        if(choice.equals("")) {
            return;
        } else if(choice.equals("(null)")) {
            choice = null;
        } else if(choice.equals("(empty)")) {
            choice = "";
        }
        pe.setAsText(choice);
    }

    /**
     * The user wants to move to a child bean.  This method figures out which
     * one.  It may be a child DConfigBean or a child property where we don't
     * have a property editor for that property type so we treat the whole
     * thing as a child bean.
     */
    private void selectChildBean(Map types, Object bean, PropertyDescriptor[] props) throws IOException, InvocationTargetException, IllegalAccessException, InstantiationException {
        DConfigBean[] cbs = null;
        PropertyDescriptor prop = null;
        int count;
        String choice;
        if(types.size()+props.length > 1) {
            count = 0;
            out.println("\nAvailable Children:");
            for(Iterator iterator = types.keySet().iterator(); iterator.hasNext();) {
                String name = (String) iterator.next();
                out.println("  ["+(++count)+"] "+name);
            }
            for(int i = 0; i < props.length; i++) {
                out.println("  ["+(++count)+"] "+props[i].getDisplayName());
            }
            while(true) {
                out.print("Select child type (1-"+(types.size()+props.length)+"): ");
                out.flush();
                choice = in.readLine();
                try {
                    int value = Integer.parseInt(choice);
                    if(value > 0 && value <= types.size()) {
                        count = 0;
                        String key = null;
                        for(Iterator iterator = types.keySet().iterator(); iterator.hasNext() && count++ < value;) {
                            key = (String) iterator.next();
                        }
                        cbs = (DConfigBean[]) types.get(key);
                        if(cbs != null) {
                            break;
                        }
                    } else if(value > types.size() && value <= (types.size()+props.length)) {
                        prop = props[value-types.size()-1];
                        break;
                    }
                } catch(NumberFormatException e) {}
            }
        } else {
            if(types.size() == 1) {
                cbs = (DConfigBean[])types.values().iterator().next();
            } else if(props.length == 1) {
                prop = props[0];
            } else {
                log.error("You've confused me.  Please try again.");
            }
        }
        if(cbs != null) {
            selectChildDConfigBean(cbs);
        } else if(prop != null) {
            selectChildProperty(bean, prop);
        }
    }

    /**
     * It turns out the user wants navigate to a child property (where we don't
     * have an editor for the property type, so we treat it as a child bean).
     * If the is a plain property, this method will just go there.  If it's an
     * indexed property, this method presents CRUD options.
     */
    private void selectChildProperty(Object bean, PropertyDescriptor prop) throws InvocationTargetException, IllegalAccessException, IOException, InstantiationException {
        //todo: consider handling indexed properties that are themselves arrays?
        if(!(prop instanceof IndexedPropertyDescriptor)) {
            beans.push(prop.getReadMethod().invoke(bean, new Object[0]));
            return;
        }
        String choice;
        Object[] values;
        while(true) {
            out.println("\nEditing list of "+prop.getDisplayName());
            values = (Object[]) prop.getReadMethod().invoke(bean, new Object[0]);
            if(values.length == 0) {
                out.println("  (list is currently empty)");
            }
            for(int i = 0; i < values.length; i++) {
                out.println("  "+(i+1)+": "+values[i]);
            }
            out.print("Action ([C]reate entry");
            if(values.length > 0) {
                out.print(" / [D]elete entry / edit entry [1"+(values.length > 1 ? "-"+values.length : "")+"]");
            }
            out.print(" / [B]ack): ");
            out.flush();
            choice = in.readLine().trim().toLowerCase();
            if(choice.equals("c")) {
                Object[] newv = (Object[])java.lang.reflect.Array.newInstance(values.getClass().getComponentType(), values.length+1);
                System.arraycopy(values, 0, newv, 0, values.length);
                newv[values.length] = values.getClass().getComponentType().newInstance();
                prop.getWriteMethod().invoke(bean, new Object[]{newv});
                continue;
            } else if(choice.equals("b")) {
                return;
            } else if(isNumber(choice)) {
                int number = Integer.parseInt(choice);
                if(number > 0 && number <= values.length) {
                    beans.push(values[number-1]);
                    return;
                }
            } else {
                log.warn("I didn't understand that");
            }
        }
    }

    /**
     * Checks whether a value entered by the user is composed entirely of digits.
     */
    private boolean isNumber(String choice) {
        for(int i=0; i<choice.length(); i++) {
            if(!Character.isDigit(choice.charAt(i))) {
                return false;
            }
        }
        return choice.length() > 0;
    }

    /**
     * It turns out the user wants to edit a child DConfigBean.  So far, we just
     * know what type of child bean they want (e.g. "one of the resource
     * references").  This method figures out which specific instance of that
     * they want to edit (identify a specific resource reference).
     */
    private void selectChildDConfigBean(DConfigBean[] cbs) throws IOException {
        String choice;
        if(cbs.length == 1) {
            beans.push(cbs[0]);
            return;
        }
        out.println("\nAvailable Children:");
        for(int i = 0; i < cbs.length; i++) {
            out.println("  ["+(i+1)+"] "+cbs[i]);
        }
        while(true) {
            out.print("Select child (1-"+cbs.length+"): ");
            out.flush();
            choice = in.readLine();
            try {
                int value = Integer.parseInt(choice);
                if(value > 0 && value <= cbs.length) {
                    beans.push(cbs[value-1]);
                    break;
                }
            } catch(NumberFormatException e) {}
        }
    }

    /**
     * Displays the user's current position in the stack of beans.  This
     * method shows everything down to the current position.  The caller
     * must add on the children of the current node.
     *
     * @return The String full of spaces representating the indentation
     *         for any children of the last bean displayed.
     */
    private String printLocation() throws IntrospectionException {
        out.println("          ---------- Editing Server-Specific DD ----------          ");
        String here = "";
        int count = 0;
        for(Iterator iterator = beans.iterator(); iterator.hasNext();) {
            ++count;
            Object temp = iterator.next();
            if(!here.equals("")) {
                out.print(here);
                out.print("+ ");
            }
            if(count == beans.size()) {out.print("[[[ ");}
            out.print(getFullName(temp));
            if(count == beans.size()) {out.print(" ]]]");}
            here = here + "  ";
            out.println();
        }
        return here;
    }

    /**
     * Gets the name of a class of beans (e.g. "Resource Reference")
     * followed by the description of the specific instances (e.g.
     * jdbc/SomeDatabase).
     */
    private String getFullName(Object bean) throws IntrospectionException {
        String name = bean.toString();
        if(name.length() > 40 || name.indexOf("@") > 0) {//todo: check whether toString has been overridden
            name = "";
        } else {
            name = " ("+name+")";
        }
        return Introspector.getBeanInfo(bean.getClass()).getBeanDescriptor().getDisplayName()+name;
    }

    /**
     * Gets the sub-list of the supplied properties that are readable, writable,
     * have a property editor, and are not on the list to specifically exclude.
     */
    private PropertyDescriptor[] getNormalProperties(PropertyDescriptor[] descriptors) {
        List list = new ArrayList(descriptors.length);
        for(int i = 0; i < descriptors.length; i++) {
            PropertyDescriptor descriptor = descriptors[i];
            if(isInvisible(descriptor) || descriptor.getReadMethod() == null || descriptor.getWriteMethod() == null || PropertyEditors.findEditor(descriptor.getPropertyType()) == null) {
                continue;
            }
            list.add(descriptors[i]);
        }
        return (PropertyDescriptor[]) list.toArray(new PropertyDescriptor[list.size()]);
    }

    /**
     * Gets the sub-list of the supplied properties that are readable, not
     * writable, and are not on the list to specifically exclude.
     */
    private PropertyDescriptor[] getReadOnly(PropertyDescriptor[] descriptors) {
        List list = new ArrayList(descriptors.length);
        for(int i = 0; i < descriptors.length; i++) {
            PropertyDescriptor descriptor = descriptors[i];
            if(isInvisible(descriptor) || descriptor.getWriteMethod() != null || descriptor.getReadMethod() == null) {
                continue;
            }
            list.add(descriptors[i]);
        }
        return (PropertyDescriptor[]) list.toArray(new PropertyDescriptor[list.size()]);
    }

    /**
     * Gets the sub-list of the supplied properties that are readable, writeable,
     * and have no property editor.  These will be treated as child properties,
     * so their properties will in turn be presented for editing.
     */
    private PropertyDescriptor[] getChildProperties(PropertyDescriptor[] descriptors) {
        List list = new ArrayList(descriptors.length);
        for(int i = 0; i < descriptors.length; i++) {
            PropertyDescriptor descriptor = descriptors[i];
            if(isInvisible(descriptor) || descriptor.getWriteMethod() == null || descriptor.getReadMethod() == null || PropertyEditors.findEditor(descriptor.getPropertyType()) != null) {
                continue;
            }
            list.add(descriptors[i]);
        }
        return (PropertyDescriptor[]) list.toArray(new PropertyDescriptor[list.size()]);
    }

    /**
     * Checks whether a property is one of the ones we want to specifically
     * ignore/suppress.
     */
    private boolean isInvisible(PropertyDescriptor descriptor) {
        return descriptor.getName().equals("class") || descriptor.getName().equals("DDBean") || descriptor.getName().equals("xpaths");
    }
}
TOP

Related Classes of org.apache.geronimo.console.cli.DConfigBeanConfigurator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.