package er.cayenne; import java.io.File; import java.io.PrintWriter; import java.io.Serializable; import java.math.BigInteger; import java.sql.Types; import java.util.Calendar; import java.util.HashMap; import java.util.Map; import org.apache.cayenne.exp.Expression; import org.apache.cayenne.map.DataMap; import org.apache.cayenne.map.DbAttribute; import org.apache.cayenne.map.DbEntity; import org.apache.cayenne.map.DbJoin; import org.apache.cayenne.map.DbRelationship; import org.apache.cayenne.map.DeleteRule; import org.apache.cayenne.map.ObjAttribute; import org.apache.cayenne.map.ObjEntity; import org.apache.cayenne.map.ObjRelationship; import org.apache.cayenne.query.SelectQuery; import org.apache.cayenne.query.SortOrder; import org.apache.cayenne.util.Util; import com.webobjects.eoaccess.EOAttribute; import com.webobjects.eoaccess.EOEntity; import com.webobjects.eoaccess.EOJoin; import com.webobjects.eoaccess.EOModel; import com.webobjects.eoaccess.EOModelGroup; import com.webobjects.eoaccess.EORelationship; import com.webobjects.eocontrol.EOClassDescription; import com.webobjects.eocontrol.EOFetchSpecification; import com.webobjects.eocontrol.EOSortOrdering; import er.extensions.eof.ERXGenericRecord; /* * ==================================================================== * * The ObjectStyle Group Software License, Version 1.0 * * Copyright (c) 2006 The ObjectStyle Group and individual authors of the * software. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. The end-user documentation included with the redistribution, if any, must * include the following acknowlegement: "This product includes software * developed by the ObjectStyle Group (http://objectstyle.org/)." Alternately, * this acknowlegement may appear in the software itself, if and wherever such * third-party acknowlegements normally appear. * * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse or * promote products derived from this software without prior written permission. * For written permission, please contact andrus@objectstyle.org. * * 5. Products derived from this software may not be called "ObjectStyle" nor * may "ObjectStyle" appear in their names without prior written permission of * the ObjectStyle Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * OBJECTSTYLE GROUP OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many individuals on * behalf of the ObjectStyle Group. For more information on the ObjectStyle * Group, please see <http://objectstyle.org/>. * */ /** * CayenneModeler (which comes with Cayenne) includes a tool to convert EOModels to Cayenne models: create a new project and then choose Tools -> "Import EOModel".<br> * I suggest trying that first. In my experience it didn't work well because the prototypes in ERPrototypes were not resolved. * <p> * This framework will allow you to convert an EOModel to a Cayenne model. * <p> * To use it just add the framework to your build path and then add this line to you application's constructor (replace MyModel with the name of your model): * <pre><code> * new er.cayenne.CayenneConverter().run(EOModelGroup.defaultGroup().modelNamed("MyModel")); * </code></pre> * <h3>Run your WO app.</h3> * This will create a Cayenne DataMap file (called MyModel.map.xml) in the root of your Sources folder * To use it you will need to run CayenneModeler and create a new project. * Then give a name to the DataDomain (top-level) node that is created in the new project. * Then choose File -> Import DataMap and select the .map.xml file that was generated. * <p> * The converter does not copy the connection dictionary from your model - you will need to re-enter that information by creating a DataNode using CayenneModeler. * <p> * The converter attempts to convert qualifiers for any fetch specifications you've defined in your model, but this should be considered just a best attempt, not guaranteed to be correct. */ public class CayenneConverter { public static void main(String[] args) { try { EOModelGroup.defaultGroup().addModelWithPath(args[0]); new CayenneConverter().run(EOModelGroup.defaultGroup().modelWithPath(args[0])); } catch (Exception e) { throw new RuntimeException(e); } } /** * Converts an EOModel to a Cayenne model / project. See the class docs for more info. * @param model */ public void run(EOModel model) { try { DataMap dataMap = new DataMap(model.name()); String sampleEntityClassName = model.entities().get(0).className(); if (sampleEntityClassName.contains(".")) { sampleEntityClassName = sampleEntityClassName.substring(0, sampleEntityClassName.lastIndexOf('.')); } dataMap.setDefaultPackage(sampleEntityClassName); dataMap.setDefaultSuperclass(ERXGenericRecord.class.getName()); for (EOEntity entity : model.entities()) { convertEntity(dataMap, entity); } File projectFolder; if (model.path().contains(".woa")) { projectFolder = new File(model.path()) .getParentFile() // Resources .getParentFile() // Contents .getParentFile() // App.woa .getParentFile() // build .getParentFile(); // project folder } else if (model.path().contains(".framework")) { projectFolder = new File(model.path()) .getParentFile() // Resources .getParentFile() // App.framework .getParentFile() // build .getParentFile(); // project folder } else { projectFolder = new File(model.path()) .getParentFile() // Resources .getParentFile(); // project folder } File sourcesFolder = new File(projectFolder.getAbsolutePath(), "Sources"); File newModelFile = new File(sourcesFolder.getAbsolutePath(), model.name() + ".map.xml"); try (PrintWriter writer = new PrintWriter(newModelFile, "UTF8")) { dataMap.encodeAsXML(writer); } System.err.println("\nWrote cayenne map (model) file to: " + newModelFile.getCanonicalPath() + "\n"); // DataNode dataNode = new DataNode(model.name()); // dataNode.setSchemaUpdateStrategy(new SkipSchemaUpdateStrategy()); // // DriverDataSource dataSource = (DriverDataSource) dataNode.getDataSource(); // dataSource.setConnectionUrl((String) model.connectionDictionary().get("URL")); // dataSource.setDriverClassName((String)model.connectionDictionary().get("driver")); // dataSource.setUserName((String)model.connectionDictionary().get("username")); // dataSource.setPassword((String)model.connectionDictionary().get("password")); // // dataNode.addDataMap(dataMap); } catch (Exception e) { throw new RuntimeException(e); } } private String dbEntityName(EOEntity entity) { String dbEntityName = entity.externalName(); if (entity.name().equalsIgnoreCase(entity.externalName())) { dbEntityName = entity.name(); // make the DbEntity match the case } else if ((entity.name() + "s").equalsIgnoreCase(entity.externalName())) { dbEntityName = entity.name() + "s"; // make the DbEntity match the case } return dbEntityName; } private void convertEntity(DataMap dataMap, EOEntity entity) { DbEntity dbEntity = new DbEntity(dbEntityName(entity)); dbEntity.setDataMap(dataMap); dataMap.addDbEntity(dbEntity); ObjEntity objEntity = new ObjEntity(entity.name()); objEntity.setDbEntity(dbEntity); objEntity.setClassName(entity.className()); objEntity.setClientClassName(entity.clientClassName()); objEntity.setSuperClassName("er.extensions.eof.ERXGenericRecord"); objEntity.setAbstract(entity.isAbstractEntity()); objEntity.setReadOnly(entity.isReadOnly()); objEntity.setDataMap(dataMap); objEntity.setDeclaredLockType(ObjEntity.LOCK_TYPE_OPTIMISTIC); dataMap.addObjEntity(objEntity); for (EOAttribute attribute : entity.attributes()) { convertAttribute(entity, dbEntity, objEntity, attribute); } for (EORelationship relationship : entity.relationships()) { convertRelationship(entity, dbEntity, objEntity, relationship); } for (String fetchSpecName : entity.fetchSpecificationNames()) { convertFetchSpecification(entity, dataMap, fetchSpecName); } } private void convertAttribute(EOEntity entity, DbEntity dbEntity, ObjEntity objEntity, EOAttribute attribute) { DbAttribute dbAttribute = null; if (!attribute.isDerived()) { dbAttribute = new DbAttribute(attribute.columnName()); dbAttribute.setEntity(dbEntity); String javaClass = getJavaClassName(attribute); int jdbcType = getSqlTypeByJava(javaClass); if (jdbcType == NOT_DEFINED) { if (javaClass.endsWith("Boolean") && attribute.width() == 5) { jdbcType = Types.VARCHAR; } else { System.out.println("Unable to find JDBC type for attribute: " + attribute); } } dbAttribute.setType(jdbcType); dbAttribute.setMaxLength(attribute.width()); if (attribute.precision() != 0) { dbAttribute.setMaxLength(attribute.precision()); } dbAttribute.setScale(attribute.scale()); dbAttribute.setMandatory(!attribute.allowsNull()); dbAttribute.setPrimaryKey(entity.primaryKeyAttributes().contains(attribute)); dbEntity.addAttribute(dbAttribute); } if (entity.classPropertyNames().contains(attribute.name())) { ObjAttribute objAttribute = new ObjAttribute(attribute.name()); objAttribute.setDbAttributePath(dbAttribute.getName()); objAttribute.setEntity(objEntity); objAttribute.setType(getJavaClassName(attribute)); // .replaceFirst("^java.lang.", "") objAttribute.setUsedForLocking(entity.attributesUsedForLocking().contains(attribute)); objEntity.addAttribute(objAttribute); } } private void convertRelationship(EOEntity entity, DbEntity dbEntity, ObjEntity objEntity, EORelationship relationship) { DbRelationship dbRelationship = new DbRelationship(relationship.name()); dbRelationship.setSourceEntity(dbEntity); dbRelationship.setTargetEntityName(dbEntityName(relationship.destinationEntity())); dbRelationship.setToMany(relationship.isToMany()); dbRelationship.setToDependentPK(relationship.propagatesPrimaryKey()); dbEntity.addRelationship(dbRelationship); // isMandatory? // relationship.joinSemantic() ? for (EOJoin join : relationship.joins()) { DbJoin dbJoin = new DbJoin(dbRelationship, join.sourceAttribute().columnName(), join.destinationAttribute().columnName()); dbRelationship.addJoin(dbJoin); } if (entity.classPropertyNames().contains(relationship.name())) { ObjRelationship objRelationship = new ObjRelationship(relationship.name()); objRelationship.setSourceEntity(objEntity); objRelationship.setTargetEntityName(relationship.destinationEntity().name()); objRelationship.setUsedForLocking(entity.attributesUsedForLocking().containsAll(relationship.sourceAttributes())); objRelationship.setDbRelationshipPath(relationship.name()); int deleteRule = 0; switch (relationship.deleteRule()) { case EOClassDescription.DeleteRuleCascade: deleteRule = DeleteRule.CASCADE; break; case EOClassDescription.DeleteRuleDeny: deleteRule = DeleteRule.DENY; break; case EOClassDescription.DeleteRuleNoAction: deleteRule = DeleteRule.NO_ACTION; break; case EOClassDescription.DeleteRuleNullify: deleteRule = DeleteRule.NULLIFY; break; } objRelationship.setDeleteRule(deleteRule); if (relationship.isToMany()) { objRelationship.setDeleteRule( DeleteRule.CASCADE); } objEntity.addRelationship(objRelationship); } } private void convertFetchSpecification(EOEntity entity, DataMap dataMap, String fetchSpecName) { EOFetchSpecification fetchSpec = entity.fetchSpecificationNamed(fetchSpecName); SelectQuery query = new SelectQuery(dataMap.getObjEntity(entity.name())); query.setName(fetchSpecName); query.setDistinct(fetchSpec.usesDistinct()); query.setFetchingDataRows(fetchSpec.fetchesRawRows()); String qualString = fetchSpec.qualifier().toString(); qualString = qualString.replace(" caseinsensitivelike ", " likeIgnoreCase "); qualString = qualString.replace(" caseInsensitiveLike ", " likeIgnoreCase "); qualString = qualString.replace(" AND ", " and "); qualString = qualString.replace(" OR ", " or "); if (qualString.contains(" like ") || qualString.contains(" likeIgnoreCase ")) { qualString = qualString.replace("*", "%"); } try { query.setQualifier(Expression.fromString(qualString)); } catch (Exception e) { System.out.println("unable to parse qualifier for fetchSpec '" + fetchSpecName + "'. qual=" + qualString + "\n" + e.getMessage()); } query.setFetchLimit(fetchSpec.fetchLimit()); //query.setResolvingInherited(fetchSpec.isDeep()); query.setStatementFetchSize(fetchSpec.fetchLimit()); for (String keyPath : fetchSpec.prefetchingRelationshipKeyPaths()) { query.addPrefetch(keyPath); } for (EOSortOrdering sortOrdering : fetchSpec.sortOrderings()) { SortOrder order = SortOrder.ASCENDING; if (sortOrdering.selector().equals(EOSortOrdering.CompareCaseInsensitiveAscending)) { order = SortOrder.ASCENDING_INSENSITIVE; } else if (sortOrdering.selector().equals(EOSortOrdering.CompareCaseInsensitiveDescending)) { order = SortOrder.DESCENDING_INSENSITIVE; } else if (sortOrdering.selector().equals(EOSortOrdering.CompareDescending)) { order = SortOrder.DESCENDING; } query.addOrdering(sortOrdering.key(), order); } dataMap.addQuery(query); } public String getJavaClassName(EOAttribute attr) { String className = attr.valueTypeClassName(); if ("java.lang.Number".equals(className) || "Number".equals(className) || "NSNumber".equals(className)) { String valueType = attr.valueType(); if (valueType == null || valueType.length() == 0) { className = java.lang.Integer.class.getName(); } else if ("B".equals(valueType)) { className = java.math.BigDecimal.class.getName(); } else if ("b".equals(valueType)) { className = java.lang.Byte.class.getName(); } else if ("d".equals(valueType)) { className = java.lang.Double.class.getName(); } else if ("f".equals(valueType)) { className = java.lang.Float.class.getName(); } else if ("i".equals(valueType)) { className = java.lang.Integer.class.getName(); } else if ("l".equals(valueType)) { className = java.lang.Long.class.getName(); } else if ("s".equals(valueType)) { className = java.lang.Short.class.getName(); } else if ("c".equals(valueType)) { className = java.lang.Boolean.class.getName(); } } else if ("NSString".equals(className)) { className = java.lang.String.class.getName(); } else if ("NSCalendarDate".equals(className) || "com.webobjects.foundation.NSTimestamp".equals(className)) { className = java.sql.Timestamp.class.getName(); // "com.webobjects.foundation.NSTimestamp"; } else if ("NSDecimalNumber".equals(className)) { String valueType = attr.valueType(); if (valueType == null || valueType.length() == 0) { className = java.lang.Integer.class.getName(); } else { className = java.math.BigDecimal.class.getName(); } } return className; } /***************************************************************** * * CODE BELOW IS COPIED FROM org.apache.cayenne.dba.TypesMapping. * WITH THE FOLLOWING LICENSE: * * 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. ****************************************************************/ /** * Returns default java.sql.Types type by the Java type name. * * @param className Fully qualified Java Class name. * @return The SQL type or NOT_DEFINED if no type found. */ public static int getSqlTypeByJava(String className) { if (className == null) { return NOT_DEFINED; } Integer type = javaSqlEnum.get(className); if (type != null) { return type.intValue(); } // try to load a Java class - some nonstandard mappings may work Class<?> aClass; try { aClass = Util.getJavaClass(className); } catch (Throwable th) { return NOT_DEFINED; } return getSqlTypeByJava(aClass); } /** * Guesses a default JDBC type for the Java class. * * @since 1.1 */ public static int getSqlTypeByJava(Class<?> javaClass) { if (javaClass == null) { return NOT_DEFINED; } // check standard mapping of class and superclasses Class<?> aClass = javaClass; while (aClass != null) { String name; if (aClass.isArray()) { name = aClass.getComponentType().getName() + "[]"; } else { name = aClass.getName(); } Object type = javaSqlEnum.get(name); if (type != null) { return ((Number) type).intValue(); } aClass = aClass.getSuperclass(); } // check non-standard JDBC types that are still supported by JPA if (javaClass.isArray()) { Class<?> elementType = javaClass.getComponentType(); if (Character.class.isAssignableFrom(elementType) || Character.TYPE.isAssignableFrom(elementType)) { return Types.VARCHAR; } else if (Byte.class.isAssignableFrom(elementType) || Byte.TYPE.isAssignableFrom(elementType)) { return Types.VARBINARY; } } if (Calendar.class.isAssignableFrom(javaClass)) { return Types.TIMESTAMP; } else if (BigInteger.class.isAssignableFrom(javaClass)) { return Types.BIGINT; } // serializable check should be the last one when all other mapping attempts // failed else if (Serializable.class.isAssignableFrom(javaClass)) { return Types.VARBINARY; } return NOT_DEFINED; } // Never use "-1" or any other normal integer, since there // is a big chance it is being reserved in java.sql.Types public static final int NOT_DEFINED = Integer.MAX_VALUE; // char constants for Java data types public static final String JAVA_LONG = "java.lang.Long"; public static final String JAVA_BYTES = "byte[]"; public static final String JAVA_BOOLEAN = "java.lang.Boolean"; public static final String JAVA_STRING = "java.lang.String"; public static final String JAVA_SQLDATE = "java.sql.Date"; public static final String JAVA_UTILDATE = "java.util.Date"; public static final String JAVA_BIGDECIMAL = "java.math.BigDecimal"; public static final String JAVA_DOUBLE = "java.lang.Double"; public static final String JAVA_FLOAT = "java.lang.Float"; public static final String JAVA_INTEGER = "java.lang.Integer"; public static final String JAVA_SHORT = "java.lang.Short"; public static final String JAVA_BYTE = "java.lang.Byte"; public static final String JAVA_TIME = "java.sql.Time"; public static final String JAVA_TIMESTAMP = "java.sql.Timestamp"; public static final String JAVA_BLOB = "java.sql.Blob"; /** * Keys: java class names, Values: SQL int type definitions from java.sql.Types */ private static final Map<String, Integer> javaSqlEnum = new HashMap<>(); static { javaSqlEnum.put(JAVA_LONG, Integer.valueOf(Types.BIGINT)); javaSqlEnum.put(JAVA_BYTES, Integer.valueOf(Types.BINARY)); javaSqlEnum.put(JAVA_BOOLEAN, Integer.valueOf(Types.BIT)); javaSqlEnum.put(JAVA_STRING, Integer.valueOf(Types.VARCHAR)); javaSqlEnum.put(JAVA_SQLDATE, Integer.valueOf(Types.DATE)); javaSqlEnum.put(JAVA_UTILDATE, Integer.valueOf(Types.DATE)); javaSqlEnum.put(JAVA_TIMESTAMP, Integer.valueOf(Types.TIMESTAMP)); javaSqlEnum.put(JAVA_BIGDECIMAL, Integer.valueOf(Types.DECIMAL)); javaSqlEnum.put(JAVA_DOUBLE, Integer.valueOf(Types.DOUBLE)); javaSqlEnum.put(JAVA_FLOAT, Integer.valueOf(Types.FLOAT)); javaSqlEnum.put(JAVA_INTEGER, Integer.valueOf(Types.INTEGER)); javaSqlEnum.put(JAVA_SHORT, Integer.valueOf(Types.SMALLINT)); javaSqlEnum.put(JAVA_BYTE, Integer.valueOf(Types.SMALLINT)); javaSqlEnum.put(JAVA_TIME, Integer.valueOf(Types.TIME)); javaSqlEnum.put(JAVA_TIMESTAMP, Integer.valueOf(Types.TIMESTAMP)); // add primitives javaSqlEnum.put("byte", Integer.valueOf(Types.TINYINT)); javaSqlEnum.put("int", Integer.valueOf(Types.INTEGER)); javaSqlEnum.put("short", Integer.valueOf(Types.SMALLINT)); javaSqlEnum.put("char", Integer.valueOf(Types.CHAR)); javaSqlEnum.put("double", Integer.valueOf(Types.DOUBLE)); javaSqlEnum.put("long", Integer.valueOf(Types.BIGINT)); javaSqlEnum.put("float", Integer.valueOf(Types.FLOAT)); javaSqlEnum.put("boolean", Integer.valueOf(Types.BIT)); } }