You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openjpa.apache.org by pp...@apache.org on 2010/07/26 21:23:49 UTC

svn commit: r979414 - in /openjpa/trunk/openjpa-tools: ./ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/openjpa/ src/main/java/org/apache/openjpa/tools/ src/main/java/org/apache/openjpa/tools/action...

Author: ppoddar
Date: Mon Jul 26 19:23:48 2010
New Revision: 979414

URL: http://svn.apache.org/viewvc?rev=979414&view=rev
Log:
OPENJPA-1745: Initial take on MigrationTool

Added:
    openjpa/trunk/openjpa-tools/
    openjpa/trunk/openjpa-tools/src/
    openjpa/trunk/openjpa-tools/src/main/
    openjpa/trunk/openjpa-tools/src/main/java/
    openjpa/trunk/openjpa-tools/src/main/java/org/
    openjpa/trunk/openjpa-tools/src/main/java/org/apache/
    openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/
    openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/
    openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/MigrationTool.java   (with props)
    openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/action/
    openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/action/Actions.java   (with props)
    openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/util/
    openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/util/CommandProcessor.java   (with props)
    openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/util/Option.java   (with props)
    openjpa/trunk/openjpa-tools/src/main/resources/
    openjpa/trunk/openjpa-tools/src/main/resources/META-INF/
    openjpa/trunk/openjpa-tools/src/main/resources/META-INF/migration-actions.xml   (with props)
    openjpa/trunk/openjpa-tools/src/main/resources/META-INF/migration-actions.xsd

Added: openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/MigrationTool.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/MigrationTool.java?rev=979414&view=auto
==============================================================================
--- openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/MigrationTool.java (added)
+++ openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/MigrationTool.java Mon Jul 26 19:23:48 2010
@@ -0,0 +1,407 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.tools;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.FileHandler;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.openjpa.tools.action.Actions;
+import org.apache.openjpa.tools.util.CommandProcessor;
+import org.apache.openjpa.tools.util.LogRecordFormatter;
+import org.apache.openjpa.tools.util.Option;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Comment;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+
+/**
+ * Converts a XML document to another XML document based on a set of
+ * actions that are themselves specified through a XML descriptor constrained by a XML Schema.
+ * <br>
+ * The concrete use case for such extreme XMLmania is to convert a set of Hibernate Mapping or 
+ * Configuration files to JPA standard mapping/configuration files.
+ * <p>
+ * Usage:<br>
+ * <code>$ java org.apache.openjpa.tools.MigrationTool -i input [-o output] [-err error output] 
+ * [-rule rule xml] [-verbose]</code>
+ * <br>
+ * Simply typing<br>
+ * <code>$ java org.apache.openjpa.tools.MigrationTool</code>
+ * <br>
+ * prints the usage of each options.  
+ * 
+ * <p>
+ * <B>Design/Implementation Note</B>:<br>
+ * The Hibernate mapping descriptor is <em>not</em> isomorphic to JPA mapping descriptor.
+ * Besides isomorphic changes such as an attribute <code>not-null="true"</code> 
+ * becoming <code>optional="false"</code>, more complex transformations can occur, 
+ * such as <br>
+ * <LI>some attributes can become elements (e.g. <code>&lt;column&gt;</code>)
+ * <LI>some elements can become attributes (e.g. <code>&lt;cache&gt;</code>
+ * <LI>some elements can split into two elements that are added at different hierarchy in
+ * the XML document (e.g. <code>&lt;complex-id&gt;</code>
+ * <LI>new elements that have no counterpart in Hibernate may be inserted 
+ * (e.g. <code>&lt;attributes&gt;</code> below <code>&lt;entity&gt;</code>)
+ * <LI>some element can be fully replaced by its own child element (e.g. <code>&lt;bag&gt;</code>)
+ * <br>
+ * These actions, wherever possible, are parameterized and defined via an 
+ * <A HREF="../../../../resources/META-INF/migration-actions.xsd">XML Schema definition</A>.
+ * The recipe to transform individual Hibernate elements are described in a 
+ *  <A HREF="../../../../resources/META-INF/migration-actions.xml">XML specification</A>
+ *  complaint to this schema.
+ * 
+ *  
+ * @author Pinaki Poddar
+ *
+ */
+public class MigrationTool  {
+    static final String W3C_XML_SCHEMA       = "http://www.w3.org/2001/XMLSchema"; 
+    static final String JAXP_SCHEMA_SOURCE   = "http://java.sun.com/xml/jaxp/properties/schemaSource";
+    static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
+    static final String LOAD_EXTERNAL_DTD    = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
+    
+    static final String ACTION_SCHMA_XSD     = "META-INF/migration-actions.xsd";
+    static final String DEFAULT_ACTIONS      = "META-INF/migration-actions.xml";
+    
+    private InputStream    _input;
+    private OutputStream   _ouput;
+    private InputStream    _rulebase;
+    
+    private Document       _target;
+    
+    /**
+     * Maps each node name of the input document to a list of actions.
+     */
+    private Map<String, List<Actions>> actionMap = new HashMap<String, List<Actions>>();
+    
+    
+    /**
+     * Standard JDK logger with a specialized formatter to augment location information
+     * in the input document from the SAX parser.
+     */
+    private Logger             _logger;
+    private LogRecordFormatter _formatter;
+
+    public static void main(String[] args) throws Exception {
+    }
+    
+    public void run(String[] args) throws Exception {
+        // sets up command processing
+        CommandProcessor cp = new CommandProcessor();
+        Option<String> inputOption    = cp.register(String.class, true,  true, "-input","-i");
+        Option<String> outputOption   = cp.register(String.class, false, true, "-output","-o");
+        Option<String> errorOption    = cp.register(String.class, false, true, "-error","-err");
+        Option<String> rulebase = cp.register(String.class, false, true, "-rules");
+        rulebase.setDefault(DEFAULT_ACTIONS);
+        Option<Boolean> verbose = cp.register(Boolean.class, false, false, "-verbose","-v");
+        
+        inputOption.setDescription("Hibernate XML file.");
+        outputOption.setDescription("Output file name. Defaults to standard console.");
+        errorOption.setDescription("Error output file name. Add a + sign at the end " +
+            "to append to an existing file. Defaults to standard error console.");
+        rulebase.setDescription("Rules specification XML file. Defaults to " + DEFAULT_ACTIONS);
+        verbose.setDescription("Prints detailed trace. Defaults to false.");
+        
+        cp.setAllowsUnregisteredOption(false);
+        if (!cp.validate(args)) {
+            System.err.println(cp.usage(MigrationTool.class));
+            System.exit(1);
+        } else {
+            cp.setFrom(args);
+        }
+        _input = getInputStream(cp.getValue(inputOption));
+        _ouput = getOutputStream(cp.getValue(outputOption));
+        _rulebase  = getInputStream(cp.getValue(rulebase));
+        
+        _logger = Logger.getLogger(getClass().getPackage().getName());
+        Handler handler = null;
+        _formatter = new LogRecordFormatter();
+        _formatter.setSource(cp.getValue(inputOption));
+        
+        if (!cp.isSet(errorOption)) {
+            handler = new ConsoleHandler();
+        } else {
+            String errorOutput = cp.getValue(errorOption);
+            if (errorOutput.endsWith("+")) {
+                handler = new FileHandler(errorOutput.substring(errorOutput.length()-1), true);
+            } else {
+                handler = new FileHandler(errorOutput, false);
+            }
+        }
+        _logger.setUseParentHandlers(false);
+        handler.setFormatter(_formatter);
+        _logger.addHandler(handler);
+       _logger.setLevel(cp.isSet(verbose) ? Level.INFO : Level.WARNING);
+        
+       
+       buildRepository(_rulebase);
+        
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setValidating(false);
+        factory.setFeature(LOAD_EXTERNAL_DTD, Boolean.FALSE);
+        DocumentBuilder parser = factory.newDocumentBuilder();
+        Document source = parser.parse(_input);
+        _target = parser.newDocument();
+        
+        // Do some work
+        Element newRoot = executeActions(_target, source.getDocumentElement(), null);
+        _target.appendChild(newRoot);
+        addSchemaToRoot();
+        addComments(newRoot);
+        
+        
+        TransformerFactory tfactory = TransformerFactory.newInstance();
+        Transformer serializer = tfactory.newTransformer();
+        serializer.setOutputProperty(OutputKeys.INDENT,     "yes");
+        serializer.setOutputProperty(OutputKeys.STANDALONE, "no");
+        serializer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+        serializer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
+        serializer.transform(new DOMSource(_target), new StreamResult(_ouput));
+        _ouput.close();
+    }
+    
+    /**
+     * Builds a repository of actions by parsing the rule base - a XML specification of rules.
+     * 
+     * @param is an input stream content of a mapping action specification.
+     * @throws Exception
+     */
+    private void buildRepository(InputStream is) throws Exception {
+        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        factory.setValidating(true);
+        factory.setNamespaceAware(true);
+        factory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
+        InputStream xsd = Thread.currentThread().getContextClassLoader().getResourceAsStream(ACTION_SCHMA_XSD);
+        if (xsd != null) {
+            factory.setAttribute(JAXP_SCHEMA_SOURCE, xsd); 
+        }
+        factory.setIgnoringElementContentWhitespace(true);
+        
+        DocumentBuilder parser = factory.newDocumentBuilder();
+        Document doc = parser.parse(is);
+        
+        Element root = doc.getDocumentElement();
+        NodeList actions = root.getElementsByTagName("actions");
+        int M = actions.getLength();
+        for (int i = 0; i < M; i++) {
+            Element element = (Element)actions.item(i);
+            String sourceTag = element.getAttribute("for");
+            _logger.info("Building action list for [" + sourceTag + "]");
+            actionMap.put(sourceTag, createAction(element));
+        }
+        is.close();
+    }
+    
+    private List<Actions> createAction(Element e) {
+        List<Actions> actions = new ArrayList<Actions>();
+        NodeList actionNodes = e.getChildNodes();
+        int N = actionNodes.getLength();
+        for (int i = 0; i < N; i++) {
+            Node node = actionNodes.item(i);;
+            if (!(node instanceof Element)) {
+                continue;
+            }
+            Element element = (Element)node;
+            Actions a = getActionFor(element);
+            actions.add(a);
+        }
+        return actions;
+    }
+    
+    Actions getActionFor(Element e) {
+        String name = e.getNodeName();
+        for (Actions.Code c : Actions.Code.values()) {
+            if (c.toString().equals(name)) {
+                return c.getTemplate(e);
+            }
+        }
+        throw new RuntimeException("No action for " + e.getNodeName());
+    }
+    
+    void addSchemaToRoot() {
+        Element root = _target.getDocumentElement();
+        String[] nvpairs = new String[] {
+                "xmlns",              "http://java.sun.com/xml/ns/persistence/orm",
+                "xmlns:xsi",          "http://www.w3.org/2001/XMLSchema-instance",
+                "xsi:schemaLocation", "http://java.sun.com/xml/ns/persistence/orm orm_2_0.xsd",
+                "version",            "2.0",
+        };
+        for (int i = 0; i < nvpairs.length; i += 2) {
+            root.setAttribute(nvpairs[i], nvpairs[i+1]);
+        }
+    }
+    
+    void addComments(Element e) {
+        String[] comments = {
+                "Generated by OpenJPA Migration Tool",
+                "Generated on  : " + new Date()
+        };
+        Node refChild = e.getFirstChild();
+        for (String c : comments) {
+            Comment comment = _target.createComment(c);
+            e.insertBefore(comment, refChild);
+        }
+    }
+    
+    /**
+     * Actions in order of their operation.
+     * @param target
+     * @param source
+     * @param current TODO
+     * @param actions
+     * @return
+     */
+    protected Element executeActions(Document target, Element source, Element current) {
+        List<Actions> actions = actionMap.get(source.getNodeName());
+        if (actions == null) {
+            _logger.severe("Element [" +  source.getNodeName() + "] is unknown");
+            throw new RuntimeException("No action for element [" +  source.getNodeName() + "]");
+        }
+        if (actions.isEmpty()) {
+            return null;
+        }
+        _logger.info("Processing source [" + source.getNodeName() + "] with " + actions.size() + " actions");
+        List<String> consumedAttrs = new ArrayList<String>();
+        List<String> consumedElements = new ArrayList<String>();
+        // first action must create a target element
+        Actions action = actions.get(0);
+        Element newElement = (Element)action.run(target, source, current, consumedAttrs, consumedElements);
+        Element root = newElement;
+        
+        for (int i = 1; i < actions.size(); i++) {
+            action = actions.get(i);
+            _logger.info("Processing source [" + source.getNodeName() + "] " + i + "-th Action " + action.getClass().getSimpleName()); 
+            Node newNode = action.run(target, source, root, consumedAttrs, consumedElements);
+            
+            if (newNode != null) {
+                switch (action.getOrder()) {
+                case RENAME_ATTR:
+                    root.setAttributeNode((Attr)newNode);
+                    break;
+                case INSERT_NODE:
+                    root.appendChild(newNode);
+                    root = (Element)newNode;
+                    break;
+                case PROMOTE_ATTR:
+                case RENAME_CHILD_NODE:
+                case SPLIT_NODE:
+                    root.appendChild(newNode);
+                    break;
+                case IGNORE_ATTR:
+                    break;
+                case RENAME_NODE:
+                    break;
+                    default:
+                        throw new RuntimeException("Result of " + action + " not handled");
+                }
+            }
+        }
+        
+        List<Element> subelements = getDirectChildren(source);
+        for (Element sub : subelements) {
+            if (consumedElements.contains(sub.getNodeName())) {
+                continue;
+            }
+            Element newNode = executeActions(target, sub, root);
+            if (newNode != null) {
+                root.appendChild(newNode);
+            } 
+        }
+        
+        Set<String> leftoverAttrNames = getAttributeNames(source);
+        leftoverAttrNames.removeAll(consumedAttrs);
+        if (!leftoverAttrNames.isEmpty()) {
+            for (String a : leftoverAttrNames) {
+                _logger.warning("Attribute [" + a + "] is not processed");
+            }
+        }
+        return newElement;
+    }
+    
+    protected List<Element> getDirectChildren(Node node) {
+        List<Element> result = new ArrayList<Element>();
+        NodeList children = node.getChildNodes();
+        
+        int N = children.getLength();
+        for (int i = 0; i < N; i++) {
+            Node child = children.item(i);
+            if (child instanceof Element && child.getParentNode() == node) {
+                result.add((Element)child);
+            }
+        }
+        return result;
+    }
+    
+    protected Set<String> getAttributeNames(Element source) {
+        Set<String> names = new HashSet<String>();
+        NamedNodeMap attrs = source.getAttributes();
+        int M = attrs.getLength();
+        for (int i = 0; i < M; i++) {
+            names.add(attrs.item(i).getNodeName());
+        }
+        return names;
+    }
+    
+    private InputStream getInputStream(String v) {
+        InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(v);
+        if (is != null)
+            return is;
+        try {
+            return new FileInputStream(new File(v));
+        } catch (Exception e) {
+            throw new RuntimeException(this + " can not convert [" + v + "] into an input stream", e);
+        }
+    }
+    
+    private OutputStream getOutputStream(String v) {
+        try {
+             return new PrintStream(new File(v));
+         } catch (Exception e) {
+             throw new RuntimeException(this + " can not convert " + v + " into an output file stream", e);
+         }
+     }
+
+}

Propchange: openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/MigrationTool.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/action/Actions.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/action/Actions.java?rev=979414&view=auto
==============================================================================
--- openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/action/Actions.java (added)
+++ openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/action/Actions.java Mon Jul 26 19:23:48 2010
@@ -0,0 +1,506 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.tools.action;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * Defines an action that operates on an input XML node in a source document to create another
+ * XML node in a target document. The output of an action may not be of the same type of
+ * its input node -- for example -- an attribute of the source can be turned into a element
+ * in the target.
+ * <br>
+ * Typical example of such action is to change the attribute values of attribute names or
+ * insert an extra node in the tree hierarchy.
+ * <br>
+ * The parameters of an action (e.g. its source element/attribute, how the values will map
+ * etc.) are supplied by a different XML element. This every concrete action has a 
+ * constructor with an XML Element as argument. 
+ * <br>
+ * The actions are enumerated and supports a comparison order. The purpose is to determine
+ * suitable order of execution for a list of actions in future.  
+ * 
+ *  
+ * @author Pinaki Poddar
+ *
+ */
+public interface Actions {//extends Comparable<Actions> {
+    /**
+     * Get the enumeration code of this action.
+     * Actions are orderable based on the ordinal values of this enumeration.
+     */
+    public Code getOrder();
+    
+    /**
+     * Runs this action.
+     * 
+     * @param targetDoc the target document.
+     * @param source the element in the source document to be processed.
+     * @param current the target element from the top of the stack. This is the
+     * element most likely to add the result of this method.
+     * @return the output of this action. Null value is permissible to signal that
+     * this action have skipped the given source.  
+     */
+    public Node run(Document targetDoc, Element source, Element current,
+            Collection<String> consumedAttrs, Collection<String> consumedElements);
+    
+    
+    /**
+     * An enumeration of different allowed actions.
+     * <br>
+     * <B>Important</B>: The string value of each enumeration must match the XML node
+     * name used for the <A HREF="../../../../../../resources/META-INF/actionrules.xml">
+     * defined rules</A>. 
+     * <br>
+     * <B>Important</B>: The enumeration order is significant. This order will determine
+     * the order of execution of a list of actions.
+     * 
+     *
+     */
+    public static enum Code {
+        RENAME_NODE      ("rename-node",       RenameNode.class),
+        RENAME_CHILD_NODE("rename-child-node", RenameChildNode.class),
+        RENAME_ATTR      ("rename-attr",       RenameAttr.class),
+        PROMOTE_ATTR     ("promote-attr",      PromoteAttr.class),
+        INSERT_NODE      ("insert-node",       InsertNode.class),
+        IGNORE_ATTR      ("ignore-attr",       IgnoreAttr.class),
+        IGNORE_NODE      ("ignore-node",       IgnoreNode.class),
+        SPLIT_NODE       ("split-node",        SplitNode.class),
+        CUSTOM_NODE      ("custom-node",       CustomNode.class);
+        
+        private final String xml;
+        private final Class<? extends Actions> template;
+        
+        private Code(String xml, Class<? extends Actions> t) {
+            this.xml = xml;
+            this.template = t;
+        }
+        
+        /**
+         * Gets the name of this code which must match the XML tags used
+         * to refer to this action.
+         */
+        public String toString() {
+            return xml;
+        }
+        
+        /**
+         * Creates a prototype action instance populated from the given element. 
+         *  
+         * @param e the element carrying the details to parameterize an action
+         * instance of this type.
+         * 
+         * @return
+         */
+        public Actions getTemplate(Element e) {
+            try {
+                return template.getConstructor(Element.class).newInstance(e);
+            } catch (InvocationTargetException e1) {
+                throw new RuntimeException(e1.getTargetException());
+            } catch (Exception e2) {
+               throw new RuntimeException(e2);
+            }
+        }
+        
+    }
+    
+    /**
+     * An abstract action to ease derivations.
+     *
+     */
+    public abstract static class Abstract implements Actions {
+        
+        protected final Element original;
+        protected final Code order;
+        
+        protected Abstract(Code o, Element s) {
+            order    = o;
+            original = s;
+        }
+        
+        
+        /**
+         * Gets the enumeration code of this action.
+         */
+        public final Code getOrder() {
+            return order;
+        }
+        
+        /**
+         * Gets the attribute value as the parameter of the action.
+         * This accessor validates that the attribute indeed exists.
+         */
+        protected String getAttribute(String attrName) {
+            if (!original.hasAttribute(attrName)) {
+                throw new IllegalArgumentException(this + " requires the input element must " +
+                        " have an attribute [" + attrName + "]");
+            }
+            return original.getAttribute(attrName);
+        }
+        
+        /**
+         * Gets the attribute value as the parameter of the action.
+         * This accessor validates that either the attribute or the default
+         * attribute indeed exists.
+         */
+        protected String getAttribute(String attrName, String defValue) {
+            if (!original.hasAttribute(attrName)) {
+                if (defValue == null || defValue.isEmpty()) {
+                    throw new IllegalArgumentException(this + " requires the input element must " +
+                        " have an attribute [" + attrName + "] or defaulted by the value of [" +
+                        defValue + "]");
+                } else {
+                    return defValue;
+                }
+            }
+            return original.getAttribute(attrName);
+        }
+        
+        protected Element getElementByName(Element base, String name, boolean mustExist) {
+            NodeList nodes = base.getElementsByTagName(name);
+            int N = nodes.getLength();
+            if (N == 0) {
+                if (mustExist)
+                    throw new IllegalArgumentException(base.getNodeName() + " must have a [" + name 
+                            + "] sub-element");
+                else
+                    return null;
+            } else if (N > 1) {
+                if (mustExist)
+                    throw new IllegalArgumentException(base.getNodeName() + " must have a single " +
+                            "[" + name + "] sub-element but has " + N + " elements");
+                else {
+                    return (Element)nodes.item(0);
+                }
+            }
+            return (Element)nodes.item(0);
+        }
+        
+    }
+    
+    /**
+     * Renames a node.
+     * This is often the first action in a series of actions. So that the subsequent
+     * actions' output can be appended in a proper tree hierarchy.
+     *
+     */
+    public static class RenameNode extends Abstract {
+        private final String sourcetName;
+        private final String targetName;
+        
+        public RenameNode(Element node) {
+            super(Code.RENAME_NODE, node);
+            targetName  = getAttribute("to", node.getParentNode().getNodeName());
+            sourcetName = getAttribute("from", targetName);
+        }
+        
+        /**
+         * Creates an element without any attribute or sub-element. 
+         */
+        public Element run(Document targetDoc, Element source, Element current,
+                Collection<String> consumedAttrs, Collection<String> consumedElements) {
+            consumedElements.add(sourcetName);
+            Element newElement = targetDoc.createElement(targetName);
+            return newElement;
+        }
+    }
+    
+    public static class RenameChildNode  extends Abstract {
+        private final String sourcetName;
+        private final String targetName;
+
+        public RenameChildNode(Element s) {
+            super(Code.RENAME_CHILD_NODE, s);
+            sourcetName = getAttribute("from");
+            targetName  = getAttribute("to", "from");
+        }
+
+        @Override
+        public Node run(Document targetDoc, Element source, Element current,
+                Collection<String> consumedAttrs, Collection<String> consumedElements) {
+            Element sourceNode = getElementByName(source, sourcetName, false);
+            if (sourceNode == null)
+                return null;
+            consumedElements.add(sourcetName);
+            Element newNode = targetDoc.createElement(targetName);
+            NamedNodeMap attrs = sourceNode.getAttributes();
+            int M = attrs.getLength();
+            for (int i = 0; i< M ; i++) {
+                Attr sourceAttr = (Attr)attrs.item(i);
+                newNode.setAttribute(sourceAttr.getNodeName(), sourceAttr.getValue());
+            }
+            return newNode;
+        }
+    }
+    
+    public static class RenameAttr  extends Abstract{
+        String sourceName;
+        String targetName;
+        private Map<String, String> _valueMap = new HashMap<String, String>();
+        
+        public RenameAttr(Element e) {
+            super(Code.RENAME_ATTR, e);
+            sourceName = e.getAttribute("from");
+            targetName = e.getAttribute("to");
+            
+            NodeList values = e.getElementsByTagName("map-value");
+            int M = values.getLength();
+            for (int i = 0; i < M; i++) {
+                Element item = (Element)values.item(i);
+                _valueMap.put(item.getAttribute("from"), item.getAttribute("to"));
+            }
+        }
+
+        public Attr run(Document targetDoc, Element source, Element current,
+                Collection<String> consumedAttrs, Collection<String> consumedElements) {
+            if (source.hasAttribute(sourceName)) {
+                consumedAttrs.add(sourceName);
+                Attr newAttr = targetDoc.createAttribute(targetName);
+                String sourceAttrValue = source.getAttribute(sourceName);
+                String newValue = _valueMap.isEmpty() ? sourceAttrValue :
+                    _valueMap.get(sourceAttrValue);
+                newAttr.setValue(newValue);
+                return newAttr;
+            } else {
+                return null;
+            }
+        }
+    }
+    
+    public static class InsertNode  extends Abstract{
+        String dummyName;
+        
+        public InsertNode(Element e) {
+            super(Code.INSERT_NODE, e);
+            dummyName = e.getAttribute("name");
+        }
+        
+        public Element run(Document targetDoc, Element source, Element current,
+                Collection<String> consumedAttrs, Collection<String> consumedElements) {
+            Element dummy = targetDoc.createElement(dummyName);
+            return dummy;
+        }
+
+    }
+    
+    /**
+     * An attribute in the source element becomes a sub-element in the target document.
+     * 
+     * @author Pinaki Poddar
+     *
+     */
+    public static class PromoteAttr  extends Abstract{
+        String sourceName;
+        String targetName;
+        String targetAttrName;
+        Map<String,String> borrow = new HashMap<String, String>();
+        private Map<String, String> _valueMap = new HashMap<String, String>();
+        
+        public PromoteAttr(Element e) {
+            super(Code.PROMOTE_ATTR, e);
+            sourceName = e.getAttribute("from");
+            targetName = e.getAttribute("to");
+            targetAttrName = e.getAttribute("as");
+            
+            NodeList values = e.getElementsByTagName("consume-attr");
+            int M = values.getLength();
+            for (int i = 0; i < M; i++) {
+                Element item = (Element)values.item(i);
+                borrow.put(item.getAttribute("from"), item.getAttribute("to"));
+            }
+            values = e.getElementsByTagName("map-value");
+            M = values.getLength();
+            for (int i = 0; i < M; i++) {
+                Element item = (Element)values.item(i);
+                _valueMap.put(item.getAttribute("from"), item.getAttribute("to"));
+            }
+        }
+        
+        public Element run(Document targetDoc, Element source, Element current,
+                Collection<String> consumedAttrs, Collection<String> consumedElements) {
+            if (!source.hasAttribute(sourceName))
+                return null;
+            consumedAttrs.add(sourceName);
+            Element newElement = targetDoc.createElement(targetName);
+            String sourceAttrValue = source.getAttribute(sourceName);
+            if (targetAttrName.isEmpty()) {
+                String targetAttrValue = _valueMap.containsKey(sourceAttrValue)
+                    ? _valueMap.get(sourceAttrValue) : sourceAttrValue;
+                newElement.setTextContent(targetAttrValue);
+            } else {
+                newElement.setAttribute(targetAttrName, sourceAttrValue);
+            
+                for (Map.Entry<String, String> entry : borrow.entrySet()) {
+                    if (source.hasAttribute(entry.getKey())) {
+                        consumedAttrs.add(entry.getKey());
+                        newElement.setAttribute(entry.getValue(), source.getAttribute(entry.getKey()));
+                    }
+                }
+            }
+            return newElement;
+        }
+    }
+    
+    
+    public static class IgnoreAttr extends Abstract {
+        public IgnoreAttr(Element e) {
+            super(Code.IGNORE_ATTR, e);
+        }
+
+        @Override
+        public Node run(Document targetDoc, Element source, Element current,
+                Collection<String> consumedAttrs, Collection<String> consumedElements) {
+            consumedAttrs.add(original.getAttribute("name"));
+            return null;
+        }
+    }
+    
+    public static class IgnoreNode extends Abstract {
+        public IgnoreNode(Element e) {
+            super(Code.IGNORE_NODE, e);
+        }
+
+        @Override
+        public Node run(Document targetDoc, Element source, Element current,
+                Collection<String> consumedAttrs, Collection<String> consumedElements) {
+            consumedElements.add(original.getAttribute("name"));
+            return null;
+        }
+    }
+    
+    /**
+     * Splits the node into two nodes. The one node is returned to the caller.
+     * Other node is added to the current nodes parent.
+     * 
+     */
+    public static class SplitNode extends Abstract {
+        String sourceName;
+        String targetName;
+        String sourceAttrName;
+        public SplitNode(Element e) {
+            super(Code.SPLIT_NODE, e);
+            sourceName = e.getAttribute("from");
+            targetName = e.getAttribute("to");
+            sourceAttrName = e.getAttribute("on");
+        }
+
+        @Override
+        public Node run(Document targetDoc, Element source, Element current,
+                Collection<String> consumedAttrs, Collection<String> consumedElements) {
+            Element forParent  = targetDoc.createElement(targetName);
+            Element sourceChild = getElementByName(source, sourceName, false);
+            if (sourceChild == null)
+                return null;
+            forParent.setAttribute(sourceAttrName, sourceChild.getAttribute(sourceAttrName));
+            current.getParentNode().insertBefore(forParent, current); 
+            
+            return null;
+        }
+    }
+    
+    /**
+     * This action is for collection field mapping such as &lt;bag&gt; or &lt;set&gt;
+     * and currently completely hard-coded.
+     * 
+     *
+     */
+    public static class CustomNode extends Abstract {
+        public CustomNode(Element e) {
+            super(Code.CUSTOM_NODE, e);
+        }
+
+        @Override
+        public Node run(Document targetDoc, Element source, Element current,
+                Collection<String> consumedAttrs, Collection<String> consumedElements) {
+            consumedAttrs.add("embed-xml");
+            consumedAttrs.add("mutable");
+            consumedAttrs.add("outer-join");
+            consumedAttrs.add("optimistic-lock");
+            consumedAttrs.add("sort");
+            
+            String realTag = "one-to-many";
+            Element realElement = getElementByName(source, realTag, false);
+            if (realElement == null) {
+                realTag = "many-to-many";
+                realElement =  getElementByName(source, realTag, true);
+            }
+            consumedElements.add(realTag);
+            Element newElement = targetDoc.createElement(realTag);
+            newElement.setAttribute("name", source.getAttribute("name"));
+            consumedAttrs.add("name");
+            if (realElement.hasAttribute("class")) {
+                newElement.setAttribute("target-entity", realElement.getAttribute("class"));
+            }
+            if (source.hasAttribute("lazy")) {
+                consumedAttrs.add("lazy");
+                newElement.setAttribute("fetch", 
+                        "true".equals(source.getAttribute("lazy")) ? "LAZY" : "EAGER");
+            }
+            if (source.hasAttribute("order-by")) {
+                consumedAttrs.add("order-by");
+                Element orderColumn = targetDoc.createElement("order-column");
+                orderColumn.setAttribute("name", source.getAttribute("order-by"));
+                newElement.appendChild(orderColumn);
+            }
+            if (source.hasAttribute("inverse") && "true".equals(source.getAttribute("inverse"))) {
+                consumedAttrs.add("inverse");
+                newElement.setAttribute("mapped-by","?");
+            }
+            
+            // TODO: Handle original "key" attribute 
+            consumedElements.add("key");
+//
+//            Element keyElement = getElementByName(source, "key", false);
+//            if (keyElement != null) {
+//                consumedElements.add("key");
+//                Element joinColumn = targetDoc.createElement("join-column");
+//                joinColumn.setAttribute("name", keyElement.getAttribute("column"));
+//                newElement.appendChild(joinColumn);
+//            }
+            if (source.hasAttribute("cascade")) {
+                consumedAttrs.add("cascade");
+                Element cascadeElement = targetDoc.createElement("cascade");
+                String[] cascades = source.getAttribute("cascade").split("\\,");
+                for (String c : cascades) {
+                    if (c.indexOf("delete-orphan") != -1) {
+                        newElement.setAttribute("orphan-removal", "true");
+                    } else {
+                        Element cascadeSubElement = targetDoc.createElement("cascade-" + 
+                                ("delete".equals(c) ? "remove" : c));
+                        cascadeElement.appendChild(cascadeSubElement);
+                    }
+                }
+                newElement.appendChild(cascadeElement);
+            }
+            
+            
+            return newElement;
+        }
+    }
+}

Propchange: openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/action/Actions.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/util/CommandProcessor.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/util/CommandProcessor.java?rev=979414&view=auto
==============================================================================
--- openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/util/CommandProcessor.java (added)
+++ openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/util/CommandProcessor.java Mon Jul 26 19:23:48 2010
@@ -0,0 +1,305 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.tools.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Processes command-line or other String-based options. <br>
+ * User can register a set of command options. Then this processor will parse a
+ * set of Strings to store the values for each of the registered options as well
+ * as optionally any unrecognized option values. <br>
+ * Provides conversion utility for the options to resolve their string input to
+ * File, Integer, InputStream etc. <br>
+ * Provides {@link #usage(Class) utility} to output available options.
+ * 
+ * @author Pinaki Poddar
+ * 
+ */
+public class CommandProcessor {
+    private final Map<Option<?>, Object> registeredOptions = new HashMap<Option<?>, Object>();
+    private final Set<Option<String>> unregisteredOptions = new HashSet<Option<String>>();
+    private boolean allowsUnregisteredOption = true;
+
+    /**
+     * Set the option values from the given arguments. All elements of the given
+     * array is <em>not</em> consumed, only till the index that appears to be a
+     * valid option.
+     * 
+     * @see #lastIndex(String[])
+     * 
+     * @param args
+     *            an array of arguments.
+     * 
+     * @return the array elements that are not consumed.
+     */
+    public String[] setFrom(String[] args) {
+        return setFrom(args, 0, args != null ? lastIndex(args) : 0);
+    }
+
+    /**
+     * Set the option values from the given arguments between the given indices.
+     * 
+     * @see #lastIndex(String[])
+     * 
+     * @param args
+     *            an array of arguments.
+     * 
+     * @return the array elements that are not consumed.
+     */
+    public String[] setFrom(String[] args, int from, int to) {
+        if (args == null)
+            return null;
+        if (args.length == 0)
+            return new String[0];
+        assertValidIndex(from, args, "Initial index " + from + " is an invalid index to " + Arrays.toString(args));
+        assertValidIndex(to, args, "Last index " + to + " is an invalid index to " + Arrays.toString(args));
+
+        int i = from;
+        for (; i <= to; i++) {
+            String c = args[i];
+            Option<?> command = findCommand(c);
+            if (command == null) {
+                throw new IllegalArgumentException(c + " is not a recongined option");
+            }
+            if (command.requiresInput()) {
+                i++;
+            }
+            if (i > to) {
+                throw new IllegalArgumentException("Command " + c + " requires a value, but no value is specified");
+            }
+            registeredOptions.put(command, command.convert(args[i]));
+        }
+        String[] remaining = new String[args.length - to];
+        System.arraycopy(args, i - 1, remaining, 0, remaining.length);
+        return remaining;
+    }
+
+    public boolean validate(String[] args) {
+        int i = 0;
+        for (Option<?> option : registeredOptions.keySet()) {
+            if (option.isMandatory() && option.requiresInput())
+                i += 2;
+        }
+        return args.length >= i;
+    }
+
+    /**
+     * Generates a short description of usage based on registered options.
+     */
+    public String usage(Class<?> cls) {
+        StringBuilder buffer = new StringBuilder();
+        buffer.append("Usage:\r\n");
+        buffer.append("  $ java ").append(cls.getName());
+
+        List<Option> sortedOptions = new ArrayList<Option>();
+        sortedOptions.addAll(registeredOptions.keySet());
+        Collections.sort(sortedOptions);
+        int L = 0;
+        for (Option<?> option : sortedOptions) {
+            L = Math.max(L, option.getName().length());
+        }
+        for (Option<?> option : sortedOptions) {
+            buffer.append(" ");
+            if (!option.isMandatory())
+                buffer.append("[");
+            buffer.append(option.getName()).append(" ").append("value");
+            if (!option.isMandatory())
+                buffer.append("]");
+        }
+        buffer.append("\r\n");
+        for (Option<?> option : sortedOptions) {
+            buffer.append("    ");
+            buffer.append(option.getName());
+            for (int i = 0; i < L - option.getName().length() + 4; i++)
+                buffer.append(" ");
+            buffer.append(option.getDescription()).append("\r\n");
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * Gets number of registered options.
+     */
+    public int getOptionCount() {
+        return registeredOptions.size();
+    }
+
+    /**
+     * Gets number of registered mandatory options.
+     */
+    public int getMandatoryOptionCount() {
+        int i = 0;
+        for (Option<?> option : registeredOptions.keySet()) {
+            if (option.isMandatory())
+                i++;
+        }
+        return i;
+    }
+
+    /**
+     * Gets the last index in the given array that can be processed as an
+     * option. The array elements are sequentially tested if they are a valid
+     * option name (i.e. starts with - character) and if valid then the next
+     * element is consumed as value, if the option requires a value. The search
+     * ends when either the array is exhausted or encounters elements that are
+     * not options.
+     * 
+     * @param args
+     *            an array of arguments
+     * @return the last index that will/can be consumed by this processor.
+     */
+    int lastIndex(String[] args) {
+        int i = 0;
+        for (; i < args.length;) {
+            Option<?> cmd = findCommand(args[i]);
+            if (cmd != null) {
+                i++;
+                if (cmd.requiresInput()) {
+                    i++;
+                }
+            } else {
+                return i;
+            }
+        }
+        return i - 1;
+    }
+    
+    public Option<String> register(boolean mandatory, boolean requiresValue, String... aliases) {
+        return register(String.class, mandatory, requiresValue, aliases);
+    }
+
+    /**
+     * Register the given aliases as a command option.
+     * 
+     * @param requiresValue
+     *            if true then the option must be specified with a value.
+     * @param aliases
+     *            strings to recognize this option. Each must begin with a dash
+     *            character.
+     * 
+     * @return the command that is registered
+     */
+    public <T> Option<T> register(Class<T> type, boolean mandatory, boolean requiresValue, String... aliases) {
+        Option<T> option = new Option<T>(type, mandatory, requiresValue, aliases);
+        registeredOptions.put(option, null);
+        return option;
+    }
+
+    /**
+     * Finds a command with the given name. If no command has been registered
+     * with the given name, but this processor allows unrecognized options, then
+     * as a result of this call, the unknown name is registered as an option.
+     * 
+     * @param option
+     *            a command alias.
+     * 
+     * @return null if the given String is not a valid command option name.
+     * 
+     */
+    Option<?> findCommand(String option) {
+        if (!Option.isValidName(option))
+            return null;
+        for (Option<?> registeredOption : registeredOptions.keySet()) {
+            if (registeredOption.match(option))
+                return registeredOption;
+        }
+        for (Option<?> unregisteredOption : unregisteredOptions) {
+            if (unregisteredOption.match(option))
+                return unregisteredOption;
+        }
+        if (allowsUnregisteredOption) {
+            Option<String> cmd = new Option<String>(String.class, false, false, option);
+            unregisteredOptions.add(cmd);
+            return cmd;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Gets all the unrecognized command options.
+     * 
+     * @return empty set if no commands are unrecognized.
+     */
+    public Set<Option<String>> getUnregisteredCommands() {
+        return Collections.unmodifiableSet(unregisteredOptions);
+    }
+
+    <T> void assertValidIndex(int i, T[] a, String message) {
+        if (i < 0 || (a != null && i >= a.length))
+            throw new ArrayIndexOutOfBoundsException(message);
+    }
+
+    /**
+     * Gets value of the option matching the given alias.
+     * 
+     * @param alias
+     *            an alias.
+     * 
+     * @return value of the given option.
+     */
+    public <T> T getValue(String alias) {
+        Option<?> cmd = findCommand(alias);
+        return (T) getValue(cmd);
+    }
+    
+    public boolean isSet(Option<?> option) {
+        return registeredOptions.get(option) != null;
+    }
+    
+
+    /**
+     * Gets value of the given option.
+     * 
+     * @param opt
+     *            an option.
+     * 
+     * @return value of the given option.
+     */
+    public <T> T getValue(Option<T> opt) {
+        Object val = registeredOptions.get(opt);
+        if (val == null)
+            val = opt.getDefaultValue();
+        return (T) val;
+    }
+
+    /**
+     * @return the allowsUnregisteredOption
+     */
+    public boolean getAllowsUnregisteredOption() {
+        return allowsUnregisteredOption;
+    }
+
+    /**
+     * @param allowsUnregisteredOption
+     *            the allowsUnregisteredOption to set
+     */
+    public void setAllowsUnregisteredOption(boolean allowsUnregisteredOption) {
+        this.allowsUnregisteredOption = allowsUnregisteredOption;
+    }
+
+}

Propchange: openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/util/CommandProcessor.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/util/Option.java
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/util/Option.java?rev=979414&view=auto
==============================================================================
--- openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/util/Option.java (added)
+++ openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/util/Option.java Mon Jul 26 19:23:48 2010
@@ -0,0 +1,219 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.openjpa.tools.util;
+
+
+/**
+ * A simple immutable object represents meta-data about a command option.
+ *  
+ * @author Pinaki Poddar
+ *
+ */
+public class Option<T> implements Comparable<Option<T>> {
+    /**
+     * The type of value this option represents.
+     */
+    private final Class<T> _valueType;
+    
+    /**
+     * Is this option mandatory to be present in the available list of options?
+     */
+    private final boolean _mandatory;
+    
+    /**
+     * Possible names of this command option.
+     * All aliases must start with a dash (<code>-</code>). 
+     * The first (zeroth) string is adjudged to be the visible name of the option.
+     */
+    private final String[] aliases;
+    
+    /**
+     * Does the option require a value?
+     */
+    private boolean requiresInput;
+    
+    /**
+     * A default value for this option.
+     * Only permissible if this option requires a value.
+     */
+    private T defValue;
+    
+    /**
+     * The original string that was converted to produce value for this option.
+     */
+    private String originalString;
+    
+    /**
+     * A description String.
+     */
+    private String _description = "";
+    
+    public static final String DASH = "-";
+    
+    /**
+     * Create a option with given aliases.
+     * 
+     * @param mandatory is this option mandatory
+     * @param requiresInput does it require a value?
+     * @param aliases strings each must start with a dash (<code>-</code>).  
+     */
+    public Option(Class<T> type, boolean mandatory, boolean requiresInput, String...aliases) {
+        if (type == String.class || type == Integer.class || type == Long.class || type == Boolean.class) {
+            _valueType = type;
+            _mandatory = mandatory;
+        } else {
+            throw new IllegalArgumentException("Does not know how to convert String value to " 
+                      + type.getName());
+        }
+        if (aliases == null || aliases.length == 0)
+            throw new IllegalArgumentException("Can not create command with null or empty aliases");
+        for (String alias : aliases) {
+            if (!isValidName(alias)) {
+                throw new IllegalArgumentException("Invalid alias [" + alias + "]. " +
+                        "Aliases must start with - followed by at least one character");
+            }
+        }
+        this.aliases = aliases;
+        this.requiresInput = requiresInput;
+    }
+    
+    /**
+     * Is this option mandatory?
+     */
+    public boolean isMandatory() {
+        return _mandatory;
+    }
+    
+    /**
+     * Affirms if the given string can be a valid option name.
+     * An option name always starts with dash and must be followed by at least one character.
+     */
+    public static boolean isValidName(String s) {
+        return s != null && s.startsWith(DASH) && s.length() > 1;
+    }
+    
+    /**
+     * Gets the first alias as the name.
+     */
+    public String getName() {
+        return aliases[0];
+    }
+    
+    /**
+     * Sets the default value for this option.
+     * 
+     * @param v a default value.
+     * 
+     * @return this command itself.
+     * 
+     * @exception IllegalStateException if this option does not require a value.
+     */
+    public Option<T> setDefault(String s) {
+        if (!requiresInput)
+            throw new IllegalStateException(this + " does not require a value. Can not set default value [" + s + "]");
+        defValue = (T)convert(s);
+        return this;
+    }
+
+    
+    /**
+     * Sets description for this option.
+     * @param desc a non-null string
+     */
+    public Option<T> setDescription(String desc) {
+        _description = desc == null ? "" : desc;
+        return this;
+    }
+    
+    public String getDescription() {
+        return _description;
+    }
+    
+    /**
+     * Affirms if the given name any of the aliases.
+     * @param name
+     * @return true if the name matches (case-sensitively) with any of the aliases.
+     */
+    public boolean match(String name) {
+        for (String alias : aliases) {
+            if (name.equals(alias))
+                return true;
+        }
+        return false;
+    }
+    
+    /**
+     * Affirms if this option requires a value.
+     */
+    public boolean requiresInput() {
+        return requiresInput;
+    }
+    
+    /**
+     * Gets the default value of this option.
+     * 
+     * @return the default value. null if no default value has been set.
+     */
+    public T getDefaultValue() {
+        return defValue;
+    }
+    
+    public String getOriginalString() {
+        return originalString;
+    }
+    
+    public Class<?> getValueType() {
+        return _valueType;
+    }
+    
+    public String toString() {
+        return getName();
+    }
+    
+    /**
+     * Converts the string to a value.
+     * 
+     */
+    public Object convert(String v) {
+        originalString = v;
+        if (_valueType == String.class)
+            return v;
+        if (_valueType == Integer.class) {
+            return Integer.parseInt(v);
+        }
+        if (_valueType == Long.class) {
+            return Long.parseLong(v);
+        }
+        if (_valueType == Boolean.class) {
+            return Boolean.parseBoolean(v);
+        }
+        return v;
+    }
+
+    @Override
+    public int compareTo(Option<T> o) {
+        if (isMandatory() && !o.isMandatory()) {
+            return -1;
+        } else if (!isMandatory() && o.isMandatory()) {
+            return 1;
+        } else {
+            return getName().compareTo(o.getName());
+        }
+    }
+}

Propchange: openjpa/trunk/openjpa-tools/src/main/java/org/apache/openjpa/tools/util/Option.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: openjpa/trunk/openjpa-tools/src/main/resources/META-INF/migration-actions.xml
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-tools/src/main/resources/META-INF/migration-actions.xml?rev=979414&view=auto
==============================================================================
--- openjpa/trunk/openjpa-tools/src/main/resources/META-INF/migration-actions.xml (added)
+++ openjpa/trunk/openjpa-tools/src/main/resources/META-INF/migration-actions.xml Mon Jul 26 19:23:48 2010
@@ -0,0 +1,143 @@
+<?xml version="1.0"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+ 
+ http://www.apache.org/licenses/LICENSE-2.0
+ 
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.   
+-->
+
+
+	<!-- =====================================================================
+	  This XML file describes how each element in Hibernate mapping file is 
+	  changed to an equivalent element in JPA Mapping file.
+	 ====================================================================== -->
+	 
+<migration-actions>	
+ 
+<actions for="hibernate-mapping">
+	<rename-node to="entity-mappings"/>
+	<promote-attr from="default-access" to="access">
+		<map-value from="field"    to="FIELD"/>
+		<map-value from="property" to="PROPERTY"/>
+	</promote-attr>
+	<ignore-attr name="auto-import"/>
+	<ignore-attr name="default-lazy"/>
+	<ignore-attr name="default-cascade"/>
+</actions>
+
+<!--  class is mapped to entity                                    -->
+<!--  inserts a dummy node attributes                              -->
+<actions for="class">
+    <rename-node to="entity"/>
+	<rename-attr from="name" to="class"/>
+	<promote-attr from="table" to="table" as="name"/>
+	<ignore-attr name="dynamic-update"/>
+	<ignore-attr name="dynamic-insert"/>
+	<ignore-attr name="mutable"/>
+	<ignore-attr name="optimistic-lock"/>
+	<ignore-attr name="polymorphism"/>
+	<ignore-attr name="select-before-update"/>
+	<ignore-attr name="type"/>
+	<ignore-node name="cache"/>
+	<insert-node name="attributes"/>
+	<split-node  from="composite-id" to="id-class" on="class"/>
+</actions>
+
+
+<actions for="id">
+    <rename-node to="id"/>
+    <rename-attr from="name" to="name"/>
+	<promote-attr from="column" to="column" as="name">
+		<consume-attr from="length" to="length"/>
+		<consume-attr from="unique" to="unique"/>
+	</promote-attr>
+	<ignore-attr name="type"/>
+</actions>
+		
+<actions for="property">
+	<rename-node to="basic"/>
+	<rename-attr from="name" to="name"/>
+	<rename-attr from="lazy" to="fetch">
+		<map-value from="true"  to="LAZY"/>
+		<map-value from="false" to="EAGER"/>
+	</rename-attr>
+	
+	<rename-attr from="not-null" to="optional">
+		<map-value from="true"  to="false"/>
+		<map-value from="false" to="true"/>
+	</rename-attr>
+	<promote-attr from="column" to="column" as="name">
+		<consume-attr from="length" to="length"/>
+		<consume-attr from="unique" to="unique"/>
+	</promote-attr>
+	<ignore-attr name="type"/>
+	<ignore-attr name="length"/>
+	<ignore-attr name="generated"/>
+	<ignore-attr name="optimistic-lock"/>
+	<ignore-attr name="unique-key"/>
+	
+</actions>
+
+<actions for="generator">
+    <rename-node to="generated-value"/>
+	<rename-attr from="class" to="strategy">
+		<map-value from="native"   to="AUTO"/>
+		<map-value from="assigned" to="IDENTITY"/>
+	</rename-attr>
+</actions>
+
+<actions for="many-to-one">
+    <rename-node to="many-to-one"/>
+    <rename-attr from="name" to="name"/>
+    <rename-attr from="class" to="target-entity"/>
+	<rename-attr from="not-null" to="optional">
+		<map-value from="true"  to="false"/>
+		<map-value from="false" to="true"/>
+	</rename-attr>
+	<promote-attr from="column" to="join-column" as="name">
+		<consume-attr from="length" to="length"/>
+	</promote-attr>
+	<rename-child-node from="column" to="join-column"/>
+</actions>
+
+<actions for="column">
+    <rename-node to="column"/>
+    <rename-attr from="name" to="name"/>
+    <rename-attr from="length" to="length"/>
+	<rename-attr from="not-null" to="nullable">
+		<map-value from="true"  to="false"/>
+		<map-value from="false" to="true"/>
+	</rename-attr>
+    <ignore-attr name="default"/>
+    <ignore-attr name="index"/>
+</actions>
+
+<actions for="composite-id">
+	<rename-node to="id"/>
+	<rename-attr from="name" to="name"/>
+	<ignore-attr name="class"/>
+	<ignore-node name="key-property"/>
+</actions>
+
+<actions for="bag">
+	<custom-node/>
+</actions>
+<actions for="set">
+	<custom-node/>
+</actions>
+
+<actions for="cache">
+</actions>
+</migration-actions>	

Propchange: openjpa/trunk/openjpa-tools/src/main/resources/META-INF/migration-actions.xml
------------------------------------------------------------------------------
    svn:eol-style = native

Added: openjpa/trunk/openjpa-tools/src/main/resources/META-INF/migration-actions.xsd
URL: http://svn.apache.org/viewvc/openjpa/trunk/openjpa-tools/src/main/resources/META-INF/migration-actions.xsd?rev=979414&view=auto
==============================================================================
--- openjpa/trunk/openjpa-tools/src/main/resources/META-INF/migration-actions.xsd (added)
+++ openjpa/trunk/openjpa-tools/src/main/resources/META-INF/migration-actions.xsd Mon Jul 26 19:23:48 2010
@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements.  See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership.  The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License.  You may obtain a copy of the License at
+ 
+ http://www.apache.org/licenses/LICENSE-2.0
+ 
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied.  See the License for the
+ specific language governing permissions and limitations
+ under the License.   
+-->
+
+<!-- =========================================================================
+     Schema for specifying actions to migrate a Hibernate Mapping file to JPA
+	 mapping file.
+	=======================================================================
+-->
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
+            attributeFormDefault="unqualified" 
+            elementFormDefault="qualified" 
+            version="1.0">
+            
+	<xsd:annotation>
+		<xsd:documentation><![CDATA[
+         This XML Schema specifies a set of actions to migrate a Hibernate mapping file
+         to a JPA mapping file. The actions are directed to elements and attributes in
+         a Hibernate Mapping descriptor, and transforms these elements/attributes by
+         renaming, changing their values or even restructuring the tree hierarchy.
+         
+         The file must be named "META-INF/migration-actions.xsd".
+         This schema is referred in a migration rule specification 
+         <migration-actions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                          xsi:schemaLocation="http://www.ibm.com/xml/ns/persistence/orm/migration-actions.xsd"
+                          version="1.0">
+                  ...
+         </migration-rules>
+         ]]>
+		</xsd:documentation>
+	</xsd:annotation>
+
+    <xsd:element name="migration-actions">
+		<xsd:complexType>
+			<xsd:sequence>
+				<xsd:element name="actions" type="action-sequence-type" minOccurs="1" maxOccurs="unbounded"/>
+			</xsd:sequence>
+		</xsd:complexType>
+    </xsd:element>
+    
+	<xsd:complexType name="action-sequence-type">
+		<xsd:choice>
+			<xsd:sequence>
+				<xsd:element name="replace-node"   minOccurs="0" maxOccurs="1" 
+					type="replace-node-type"/>
+				<xsd:element name="rename-node"    minOccurs="0" maxOccurs="1" 
+					type="rename-node-type"/>
+				<xsd:element name="rename-attr"    minOccurs="0" maxOccurs="unbounded" 
+					type="rename-attr-type"/>
+				<xsd:element name="promote-attr"   minOccurs="0" maxOccurs="unbounded" 
+					type="promote-attr-type"/>
+				<xsd:element name="rename-child-node"    minOccurs="0" maxOccurs="unbounded" 
+					type="rename-child-node-type"/>
+				<xsd:element name="ignore-attr"    minOccurs="0" maxOccurs="unbounded" 
+					type="ignore-attr-type"/>
+				<xsd:element name="ignore-node"    minOccurs="0" maxOccurs="unbounded" 
+					type="ignore-node-type"/>
+				<xsd:element name="insert-node"    minOccurs="0" maxOccurs="unbounded" 
+					type="insert-node-type"/>
+				<xsd:element name="split-node"    minOccurs="0" maxOccurs="unbounded" 
+					type="split-node-type"/>
+			</xsd:sequence>
+			
+			<xsd:element name="custom-node" minOccurs="1" maxOccurs="1"/>
+		</xsd:choice>
+			<xsd:attribute name="for" type="xsd:string" use="required"/>
+		</xsd:complexType>
+	
+	<!-- Replace an element with one of its sub-element.                          -->
+	<!-- Used for collection container tags such as bag/set  of Hibernate         -->
+	<xsd:complexType name="replace-node-type">
+		<xsd:attribute name="with" type="xsd:string"  use="required"/>
+	</xsd:complexType>
+	
+	<!-- Ignores an attribute.  -->
+	<xsd:complexType name="ignore-attr-type">
+		<xsd:attribute name="name" type="xsd:string"  use="required"/>
+		<xsd:attribute name="warn" type="xsd:boolean"/>
+	</xsd:complexType>
+	
+	<!-- Ignores a sub-element.  -->
+	<xsd:complexType name="ignore-node-type">
+		<xsd:attribute name="name" type="xsd:string"  use="required"/>
+		<xsd:attribute name="warn" type="xsd:boolean"/>
+	</xsd:complexType>
+
+	<!-- Ignore a node. Often the first action in the list of actions  -->
+	<xsd:complexType name="rename-node-type">
+		<xsd:attribute name="to" type="xsd:string"/>
+	</xsd:complexType>
+	
+	<!-- Renames a attribute. -->
+	<xsd:complexType name="rename-attr-type">
+		<xsd:sequence>
+			<xsd:element name="map-value" minOccurs="0" maxOccurs="unbounded" type="map-value-type"/>
+		</xsd:sequence>
+		<xsd:attribute name="from" type="xsd:string" use="required"/>
+		<xsd:attribute name="to" type="xsd:string"/>
+	</xsd:complexType>
+	
+	<!-- Promotes an attribute as a sub-element. The sub-element may consume some of -->
+	<!-- the attributes of the parent node                                           -->
+	<xsd:complexType name="promote-attr-type">
+		<xsd:sequence>
+			<xsd:choice>
+				<xsd:element name="consume-attr" minOccurs="0" maxOccurs="unbounded" type="consume-attr-type"/>
+				<xsd:element name="map-value"    minOccurs="0" maxOccurs="unbounded" type="map-value-type"/>
+		    </xsd:choice>
+		</xsd:sequence>
+		<xsd:attribute name="from" type="xsd:string" use="required"/>
+		<xsd:attribute name="to"   type="xsd:string"/>
+		<xsd:attribute name="as"   type="xsd:string"/>
+	</xsd:complexType>
+	
+	<xsd:complexType name="split-node-type">
+		<xsd:attribute name="from" type="xsd:string" use="required"/>
+		<xsd:attribute name="to"   type="xsd:string"/>
+		<xsd:attribute name="on"   type="xsd:string" use="required"/>
+	</xsd:complexType>
+	
+	<!-- Inserts a 'dummy' sub-element that had no source counterpart. -->
+	<xsd:complexType name="insert-node-type">
+		<xsd:attribute name="name" type="xsd:string" use="required"/>
+	</xsd:complexType>
+	
+	<!-- Renames a child node -->
+	<xsd:complexType name="rename-child-node-type">
+		<xsd:attribute name="from" type="xsd:string" use="required"/>
+		<xsd:attribute name="to"   type="xsd:string" use="required"/>
+	</xsd:complexType>
+
+	<!-- Maps an attribute value to another  -->
+	<xsd:complexType name="map-value-type">
+		<xsd:attribute name="from" type="xsd:string" use="required"/>
+		<xsd:attribute name="to"   type="xsd:string" use="required"/>
+	</xsd:complexType>
+	
+	<!-- Maps an attribute value to another while transferring it from parent to a child -->
+	<xsd:complexType name="consume-attr-type">
+		<xsd:sequence>
+			<xsd:element name="map-value" minOccurs="0" maxOccurs="unbounded" type="map-value-type"/>
+		</xsd:sequence>
+		<xsd:attribute name="from" type="xsd:string" use="required"/>
+		<xsd:attribute name="to"   type="xsd:string" use="required"/>
+	</xsd:complexType>
+	
+</xsd:schema>