package pt.ist.fenixframework.pstm.repository; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.Map.Entry; import org.joda.time.DateTime; import pt.ist.fenixframework.FenixFramework; import dml.DomainClass; import dml.DomainEntity; import dml.DomainModel; import dml.DomainRelation; import dml.Role; import dml.Slot; public class OidSqlGenerator { private static final Set<DomainClassEntry> domainClassEntries = new HashSet<DomainClassEntry>(); private static class DomainClassEntry { private DomainClassEntry superDomainClassEntry; private final DomainClass domainClass; private Set<DomainClassEntry> subDomainClassEntries = new HashSet<DomainClassEntry>(); public DomainClassEntry(final DomainClass domainClass) { final DomainClassEntry domainClassEntry = findDomainClassEntry(domainClass); if (domainClassEntry != null) { throw new Error("Domain class: " + domainClass.getFullName() + " was already registered."); } this.domainClass = domainClass; domainClassEntries.add(this); final DomainClass superDomainClass = (DomainClass) domainClass.getSuperclass(); final DomainClassEntry superDomainClassEntry = findDomainClassEntry(superDomainClass); if (superDomainClassEntry != null) { this.superDomainClassEntry = superDomainClassEntry; superDomainClassEntry.subDomainClassEntries.add(this); } for (final DomainClassEntry someDomainClassEntry : domainClassEntries) { if (someDomainClassEntry.domainClass.getSuperclass() == domainClass) { someDomainClassEntry.superDomainClassEntry = this; subDomainClassEntries.add(someDomainClassEntry); } } } private static DomainClassEntry findDomainClassEntry(final DomainClass domainClass) { for (final DomainClassEntry domainClassEntry : domainClassEntries) { if (domainClassEntry.domainClass == domainClass) { return domainClassEntry; } } return null; } } private static abstract class Writer { protected abstract String getFilename(); protected abstract void writeContents(final FileWriter fileWriter) throws IOException; public void write() throws IOException { final FileWriter fileWriter = new FileWriter(getFilename()); fileWriter.append("set autocommit = 0;\n"); fileWriter.append("begin;\n\n"); writeContents(fileWriter); fileWriter.append("\ncommit;\n"); fileWriter.close(); } } private static class AlterTableRegistry extends Writer { private final Map<String, Set<String>> map = new TreeMap<String, Set<String>>(); public void addOidColumn(final String tablename) { addColumn(tablename, "OID"); } public void addColumn(final String tablename, final String columnName) { addColumnDefinition(tablename, columnName); } private void addColumnDefinition(final String tablename, final String columnName) { Set<String> columnNames = map.get(tablename); if (columnNames == null) { columnNames = new TreeSet<String>(); map.put(tablename, columnNames); } columnNames.add(columnName); } @Override protected String getFilename() { return "/tmp/alterTables.sql"; } @Override protected void writeContents(final FileWriter fileWriter) throws IOException { for (final Entry<String, Set<String>> entry : map.entrySet()) { final String tablename = entry.getKey(); final Set<String> columnNames = entry.getValue(); fileWriter.append("alter table "); fileWriter.append(tablename); boolean isFirst = true; for (final String columnName : columnNames) { if (isFirst) { isFirst = false; } else { fileWriter.append(","); } fileWriter.append("\n add column "); fileWriter.append(columnName); fileWriter.append(" bigint unsigned default null,\n add index ("); fileWriter.append(columnName); fileWriter.append(")"); } fileWriter.append(";\n"); } } } private static class UpdateRegistry extends Writer { private final Map<String, String> mapSimple = new TreeMap<String, String>(); private final Map<String, String> mapFamily = new TreeMap<String, String>(); private final List<String[]> mapKeys = new ArrayList<String[]>(); private void addClassnameAndTableForUpdate(final String domainClassName, final String tablename) { mapSimple.put(domainClassName, tablename); } private void addClassnameAndTableForUpdateInHierchy(final String domainClassName, final String tablename) { mapFamily.put(domainClassName, tablename); } private void addUpdateKeyInstruction(final DomainClass domainClass, final String tablename, final String otherTablename, final String key, final String newKey) { final String domainClassName = domainClass == null ? null : domainClass.getFullName(); mapKeys.add(new String[] { domainClassName, tablename, otherTablename, newKey, key }); } @Override protected String getFilename() { return "/tmp/updates.sql"; } protected void writeSelect(final FileWriter fileWriter, final String domainClassName, final String tablename) throws IOException { fileWriter .append("select @xpto:=null;\nselect @xpto:=FF$DOMAIN_CLASS_INFO.DOMAIN_CLASS_ID from FF$DOMAIN_CLASS_INFO where FF$DOMAIN_CLASS_INFO.DOMAIN_CLASS_NAME = '"); fileWriter.append(domainClassName); fileWriter.append("';\n"); } protected void writeUpdate(final FileWriter fileWriter, final String domainClassName, final String tablename, final boolean appendConcreteClassClause) throws IOException { fileWriter.append("update "); fileWriter.append(tablename); fileWriter.append(" set OID = (@xpto << 32) + ID_INTERNAL"); final DomainClass domainClass = domainModel.findClass(domainClassName); for (final Slot slot : domainClass.getSlotsList()) { final String typeName = slot.getTypeName(); if (DateTime.class.getName().equals(typeName) || DateTime.class.getSimpleName().equals(typeName)) { final String columnName = getSqlName(slot.getName()); fileWriter.append(", "); fileWriter.append(columnName); fileWriter.append(" = "); fileWriter.append(columnName); } } if (appendConcreteClassClause) { fileWriter.append(" where OJB_CONCRETE_CLASS = '"); fileWriter.append(domainClassName); fileWriter.append("'"); } fileWriter.append(";\n"); } @Override protected void writeContents(final FileWriter fileWriter) throws IOException { for (final Entry<String, String> entry : mapSimple.entrySet()) { final String domainClassName = entry.getKey(); final String tablename = entry.getValue(); writeSelect(fileWriter, domainClassName, tablename); writeUpdate(fileWriter, domainClassName, tablename, false); } for (final Entry<String, String> entry : mapFamily.entrySet()) { final String domainClassName = entry.getKey(); final String tablename = entry.getValue(); writeSelect(fileWriter, domainClassName, tablename); writeUpdate(fileWriter, domainClassName, tablename, true); } for (final String[] strings : mapKeys) { final String domainClassName = strings[0]; final String tablename = strings[1]; final String otherTablename = strings[2]; final String newKey = strings[3]; final String key = strings[4]; fileWriter.append("update "); fileWriter.append(tablename); fileWriter.append(" as t1, "); fileWriter.append(otherTablename); fileWriter.append(" as t2 set t1."); fileWriter.append(newKey); fileWriter.append(" = t2.OID"); if (domainClassName != null) { final DomainClass domainClass = domainModel.findClass(domainClassName); for (final Slot slot : domainClass.getSlotsList()) { final String typeName = slot.getTypeName(); if (DateTime.class.getName().equals(typeName) || DateTime.class.getSimpleName().equals(typeName)) { final String columnName = getSqlName(slot.getName()); fileWriter.append(", t1."); fileWriter.append(columnName); fileWriter.append(" = t1."); fileWriter.append(columnName); } } } fileWriter.append(" where t2.ID_INTERNAL = t1."); fileWriter.append(key); if (domainClassName != null && mapFamily.containsKey(domainClassName)) { final DomainClass domainClass = domainModel.findClass(domainClassName); final DomainClassEntry domainClassEntry = DomainClassEntry.findDomainClassEntry(domainClass); fileWriter.append(" and t1.OJB_CONCRETE_CLASS in ('"); fileWriter.append(domainClassName); fileWriter.append("'"); appendSubEntries(fileWriter, domainClassEntry.subDomainClassEntries); fileWriter.append(")"); } fileWriter.append(";\n"); } } private void appendSubEntries(final FileWriter fileWriter, final Set<DomainClassEntry> subDomainClassEntries) throws IOException { for (final DomainClassEntry domainClassEntry : subDomainClassEntries) { fileWriter.append(", '"); fileWriter.append(domainClassEntry.domainClass.getFullName()); fileWriter.append("'"); appendSubEntries(fileWriter, domainClassEntry.subDomainClassEntries); } } } private static DomainModel domainModel = null; private static final AlterTableRegistry alterTableRegistry = new AlterTableRegistry(); private static final UpdateRegistry updateRegistry = new UpdateRegistry(); public static void generate() throws IOException { domainModel = FenixFramework.getDomainModel(); for (final DomainClass domainClass : domainModel.getDomainClasses()) { new DomainClassEntry(domainClass); } for (final DomainClass domainClass : domainModel.getDomainClasses()) { final int domainClassHierarchyLevel = calculateHierarchyLevel(domainClass); if (domainClassHierarchyLevel == 0) { final Slot slot = domainClass.findSlot("ojbConcreteClass"); if (slot == null) { writeSqlInstructions(domainClass); } else { writeSqlInstructions(domainModel, domainClass); } } writeKeySqlInstructions(domainClass); } alterTableRegistry.write(); updateRegistry.write(); } private static void writeSqlInstructions(final DomainModel domainModel, final DomainClass domainClass) throws IOException { final String tablename = getTableName(domainClass.getName()); alterTableRegistry.addOidColumn(tablename); for (final DomainClass otherDomainClass : domainModel.getDomainClasses()) { final int domainClassHierarchyLevel = calculateHierarchyLevel(otherDomainClass); if (domainClassHierarchyLevel > 0) { final DomainClass otherDomainObjectDescendent = findDirectDomainObjectDecendent(otherDomainClass); if (otherDomainObjectDescendent == domainClass) { updateRegistry.addClassnameAndTableForUpdateInHierchy(otherDomainClass.getFullName(), tablename); } } } } private static void writeSqlInstructions(final DomainClass domainClass) throws IOException { final String tablename = getTableName(domainClass.getName()); alterTableRegistry.addOidColumn(tablename); updateRegistry.addClassnameAndTableForUpdate(domainClass.getFullName(), tablename); } private static void writeKeySqlInstructions(final DomainClass domainClass) throws IOException { final String tablename = getTopLevelTableName(domainClass); if (tablename == null) { return; } for (final Role role : domainClass.getRoleSlotsList()) { final String otherTablename = getTopLevelTableName((DomainClass) role.getType()); if (role.getMultiplicityUpper() == 1) { final String key = getKeyFromRole(role); final String newKey = getNewKeyFromRole(role); alterTableRegistry.addColumn(tablename, newKey); updateRegistry.addUpdateKeyInstruction(domainClass, tablename, otherTablename, key, newKey); } else if (role.getMultiplicityUpper() == -1) { if (role.getOtherRole().getMultiplicityUpper() == -1) { writeIndirectionKeySqlInstructions(role.getRelation()); } } else if (role.getOtherRole().getMultiplicityUpper() != 1) { // Probably never happens because people don't use it; // if it does we need to know about it now... throw new Error("Not yet suported... place implementation here."); } } } private static final Set<DomainRelation> processedRelations = new HashSet<DomainRelation>(); private static void writeIndirectionKeySqlInstructions(final DomainRelation relation) throws IOException { if (processedRelations.contains(relation)) { return; } processedRelations.add(relation); final String tablename = getTableName(relation.getFullName()); for (final Role role : relation.getRoles()) { final String newKey = getNewIndirectionKeyFromRole(role); alterTableRegistry.addColumn(tablename, newKey); } for (final Role role : relation.getRoles()) { final String otherTablename = getTopLevelTableName((DomainClass) role.getType()); final String key = getIndirectionKeyFromRole(role); final String newKey = getNewIndirectionKeyFromRole(role); updateRegistry.addUpdateKeyInstruction(null, tablename, otherTablename, key, newKey); } } private static DomainClass findDirectDomainObjectDecendent(final DomainClass domainClass) { final int domainClassHierarchyLevel = calculateHierarchyLevel(domainClass); return domainClassHierarchyLevel == 1 ? domainClass : findDirectDomainObjectDecendent((DomainClass) domainClass .getSuperclass()); } private static int calculateHierarchyLevel(final DomainClass domainClass) { final DomainEntity domainEntity = domainClass.getSuperclass(); return domainEntity == null || !isDomainClass(domainEntity) ? 0 : calculateHierarchyLevel((DomainClass) domainEntity) + 1; } private static boolean isDomainClass(final DomainEntity domainEntity) { return domainEntity instanceof DomainClass; } private static String getSqlName(final String name) { final StringBuilder stringBuilder = new StringBuilder(); boolean isFirst = true; for (final char c : name.toCharArray()) { if (isFirst) { isFirst = false; stringBuilder.append(Character.toUpperCase(c)); } else { if (Character.isUpperCase(c)) { stringBuilder.append('_'); stringBuilder.append(c); } else { stringBuilder.append(Character.toUpperCase(c)); } } } return stringBuilder.toString(); } private static String getTopLevelTableName(final DomainClass domainClass) { final int domainClassHierarchyLevel = calculateHierarchyLevel(domainClass); if (domainClassHierarchyLevel == 0) { return getTableName(domainClass.getName()); } if (domainClass.getSuperclass() == null) { return null; } return getTopLevelTableName((DomainClass) domainClass.getSuperclass()); } private static String getTableName(final String name) { return getSqlName(name); } private static String getAppendedSqlName(final String prefix, final String name) { return prefix + getSqlName(name); } private static String getKeyFromRole(final Role role) { return getAppendedSqlName("KEY_", role.getName()); } private static String getNewKeyFromRole(final Role role) { return getAppendedSqlName("OID_", role.getName()); } private static String getIndirectionKeyFromRole(final Role role) { final String newKey = getAppendedSqlName("KEY_", role.getType().getName()); return hasMutualTypeInRelation(role) ? newKey + "_" + getSqlName(role.getName()) : newKey; } private static String getNewIndirectionKeyFromRole(final Role role) { final String newKey = getAppendedSqlName("OID_", role.getType().getName()); return hasMutualTypeInRelation(role) ? newKey + "_" + getSqlName(role.getName()) : newKey; } private static boolean hasMutualTypeInRelation(final Role role) { final DomainEntity domainEntity = role.getType(); for (final Role otherRole : role.getRelation().getRoles()) { final DomainEntity otherDomainEntity = otherRole.getType(); if (!role.getName().equals(otherRole.getName()) && domainEntity.getFullName().equals(otherDomainEntity.getFullName())) { return true; } } return false; } }