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><column></code>)
+ * <LI>some elements can become attributes (e.g. <code><cache></code>
+ * <LI>some elements can split into two elements that are added at different hierarchy in
+ * the XML document (e.g. <code><complex-id></code>
+ * <LI>new elements that have no counterpart in Hibernate may be inserted
+ * (e.g. <code><attributes></code> below <code><entity></code>)
+ * <LI>some element can be fully replaced by its own child element (e.g. <code><bag></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 <bag> or <set>
+ * 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>