/* * Copyright 2007 - 2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.sf.jailer.datamodel; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; 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.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import org.apache.log4j.Logger; import net.sf.jailer.ExecutionContext; import net.sf.jailer.JailerVersion; import net.sf.jailer.database.Session; import net.sf.jailer.datamodel.filter_template.Clause; import net.sf.jailer.datamodel.filter_template.FilterTemplate; import net.sf.jailer.extractionmodel.ExtractionModel; import net.sf.jailer.extractionmodel.ExtractionModel.AdditionalSubject; import net.sf.jailer.restrictionmodel.RestrictionModel; import net.sf.jailer.subsetting.ScriptFormat; import net.sf.jailer.util.CsvFile; import net.sf.jailer.util.CsvFile.LineFilter; import net.sf.jailer.util.LayoutStorage; import net.sf.jailer.util.PrintUtil; import net.sf.jailer.util.SqlUtil; /** * Relational data model. * * @author Ralf Wisser */ public class DataModel { public static final String TABLE_CSV_FILE = "table.csv"; public static final String MODELNAME_CSV_FILE = "modelname.csv"; /** * Maps table-names to tables. */ private Map<String, Table> tables = new HashMap<String, Table>(); /** * Maps table display names to tables. */ private Map<String, Table> tablesByDisplayName = new HashMap<String, Table>(); /** * Maps tables to display names. */ private Map<Table, String> displayName = new HashMap<Table, String>(); /** * Maps association-names to associations; */ public Map<String, Association> namedAssociations = new TreeMap<String, Association>(); /** * The restriction model. */ private RestrictionModel restrictionModel; /** * Internal version number. Incremented on each modification. */ public long version = 0; /** * The execution context. */ private final ExecutionContext executionContext; /** * Default model name. */ public static final String DEFAULT_NAME = "New Model"; /** * For creation of primary-keys. */ private final PrimaryKeyFactory primaryKeyFactory; /** * Gets name of data model folder. */ public static String getDatamodelFolder(ExecutionContext executionContext) { return executionContext.getQualifiedDatamodelFolder(); } /** * Gets name of file containing the table definitions. */ public static String getTablesFile(ExecutionContext executionContext) { return getDatamodelFolder(executionContext) + File.separator + TABLE_CSV_FILE; } /** * Gets name of file containing the model name */ public static String getModelNameFile(ExecutionContext executionContext) { return getDatamodelFolder(executionContext) + File.separator + MODELNAME_CSV_FILE; } /** * Gets name of file containing the display names. */ public static String getDisplayNamesFile(ExecutionContext executionContext) { return getDatamodelFolder(executionContext) + File.separator + "displayname.csv"; } /** * Gets name of file containing the column definitions. */ public static String getColumnsFile(ExecutionContext executionContext) { return getDatamodelFolder(executionContext) + File.separator + "column.csv"; } /** * Gets name of file containing the association definitions. */ public static String getAssociationsFile(ExecutionContext executionContext) { return getDatamodelFolder(executionContext) + File.separator + "association.csv"; } /** * List of tables to be excluded from deletion. */ public static String getExcludeFromDeletionFile(ExecutionContext executionContext) { return getDatamodelFolder(executionContext) + File.separator + "exclude-from-deletion.csv"; } /** * Name of file containing the version number. */ public static String getVersionFile(ExecutionContext executionContext) { return getDatamodelFolder(executionContext) + File.separator + "version.csv"; } /** * Export modus, SQL or XML. (GUI support). */ private String exportModus; /** * Holds XML settings for exportation into XML files. */ public static class XmlSettings { public String datePattern = "yyyy-MM-dd"; public String timestampPattern = "yyyy-MM-dd-HH.mm.ss"; public String rootTag = "rowset"; } /** * XML settings for exportation into XML files. */ private XmlSettings xmlSettings = new XmlSettings(); /** * Name of the model. */ private String name; /** * Time of last modification. */ private Long lastModified; /** * The logger. */ private static final Logger _log = Logger.getLogger(DataModel.class); /** * Gets a table by name. * * @param name the name of the table * @return the table or <code>null</code> iff no table with the name exists */ public Table getTable(String name) { return tables.get(name); } /** * Gets a table by display name. * * @param displayName the display name of the table * @return the table or <code>null</code> iff no table with the display name exists */ public Table getTableByDisplayName(String displayName) { return tablesByDisplayName.get(displayName); } /** * Gets name of the model. * * @return name of the model */ public String getName() { return name; } /** * Gets time of last modification. * * @return time of last modification */ public Long getLastModified() { return lastModified; } /** * Gets display name of a table * * @param table the table * @return the display name of the table */ public String getDisplayName(Table table) { String displayName = this.displayName.get(table); if (displayName == null) { return table.getName(); } return displayName; } /** * Gets all tables. * * @return a collection of all tables */ public Collection<Table> getTables() { return tables.values(); } /** * Reads in <code>table.csv</code> and <code>association.csv</code> * and builds the relational data model. */ public DataModel(PrimaryKeyFactory primaryKeyFactory, Map<String, String> sourceSchemaMapping, ExecutionContext executionContext) throws IOException { this(null, null, sourceSchemaMapping, null, primaryKeyFactory, executionContext, false); } /** * Reads in <code>table.csv</code> and <code>association.csv</code> * and builds the relational data model. */ public DataModel(ExecutionContext executionContext) throws IOException { this(null, null, new PrimaryKeyFactory(), executionContext); } /** * Reads in <code>table.csv</code> and <code>association.csv</code> * and builds the relational data model. */ public DataModel(Map<String, String> sourceSchemaMapping, ExecutionContext executionContext, boolean failOnMissingTables) throws IOException { this(null, null, sourceSchemaMapping, null, new PrimaryKeyFactory(), executionContext, failOnMissingTables); } /** * Reads in <code>table.csv</code> and <code>association.csv</code> * and builds the relational data model. * * @param additionalTablesFile table file to read too * @param additionalAssociationsFile association file to read too */ public DataModel(String additionalTablesFile, String additionalAssociationsFile, PrimaryKeyFactory primaryKeyFactory, ExecutionContext executionContext) throws IOException { this(additionalTablesFile, additionalAssociationsFile, new HashMap<String, String>(), null, primaryKeyFactory, executionContext, false); } /** * Reads in <code>table.csv</code> and <code>association.csv</code> * and builds the relational data model. * * @param additionalTablesFile table file to read too * @param additionalAssociationsFile association file to read too */ public DataModel(String additionalTablesFile, String additionalAssociationsFile, ExecutionContext executionContext) throws IOException { this(additionalTablesFile, additionalAssociationsFile, new HashMap<String, String>(), null, new PrimaryKeyFactory(), executionContext, false); } /** * Reads in <code>table.csv</code> and <code>association.csv</code> * and builds the relational data model. * * @param additionalTablesFile table file to read too * @param additionalAssociationsFile association file to read too */ public DataModel(String additionalTablesFile, String additionalAssociationsFile, Map<String, String> sourceSchemaMapping, LineFilter assocFilter, ExecutionContext executionContext) throws IOException { this(additionalTablesFile, additionalAssociationsFile, sourceSchemaMapping, assocFilter, new PrimaryKeyFactory(), executionContext, false); } /** * Reads in <code>table.csv</code> and <code>association.csv</code> * and builds the relational data model. * * @param additionalTablesFile table file to read too * @param additionalAssociationsFile association file to read too * @throws IOException */ public DataModel(String additionalTablesFile, String additionalAssociationsFile, Map<String, String> sourceSchemaMapping, LineFilter assocFilter, PrimaryKeyFactory primaryKeyFactory, ExecutionContext executionContext, boolean failOnMissingTables) throws IOException { this.executionContext = executionContext; this.primaryKeyFactory = primaryKeyFactory; try { List<String> excludeFromDeletion = new ArrayList<String>(); PrintUtil.loadTableList(excludeFromDeletion, openModelFile(new File(DataModel.getExcludeFromDeletionFile(executionContext)), executionContext)); // tables File tabFile = new File(getTablesFile(executionContext)); InputStream nTablesFile = openModelFile(tabFile, executionContext); if (failOnMissingTables && nTablesFile == null) { throw new RuntimeException("Datamodel not found: " + executionContext.getDataModelURL()); } CsvFile tablesFile = new CsvFile(nTablesFile, null, tabFile.getPath(), null); List<CsvFile.Line> tableList = new ArrayList<CsvFile.Line>(tablesFile.getLines()); if (additionalTablesFile != null) { tableList.addAll(new CsvFile(new File(additionalTablesFile)).getLines()); } for (CsvFile.Line line: tableList) { boolean defaultUpsert = "Y".equalsIgnoreCase(line.cells.get(1)); List<Column> pk = new ArrayList<Column>(); int j; for (j = 2; j < line.cells.size() && line.cells.get(j).toString().length() > 0; ++j) { String col = line.cells.get(j).trim(); try { pk.add(Column.parse(col)); } catch (Exception e) { throw new RuntimeException("unable to load table '" + line.cells.get(0) + "'. " + line.location, e); } } String mappedSchemaTableName = SqlUtil.mappedSchema(sourceSchemaMapping, line.cells.get(0)); Table table = new Table(mappedSchemaTableName, primaryKeyFactory.createPrimaryKey(pk), defaultUpsert, excludeFromDeletion.contains(mappedSchemaTableName)); table.setAuthor(line.cells.get(j + 1)); table.setOriginalName(line.cells.get(0)); if (tables.containsKey(mappedSchemaTableName)) { if (additionalTablesFile == null) { throw new RuntimeException("Duplicate table name '" + mappedSchemaTableName + "'"); } } tables.put(mappedSchemaTableName, table); } // columns File colFile = new File(getColumnsFile(executionContext)); InputStream is = openModelFile(colFile, executionContext); if (is != null) { CsvFile columnsFile = new CsvFile(is, null, colFile.getPath(), null); List<CsvFile.Line> columnsList = new ArrayList<CsvFile.Line>(columnsFile.getLines()); for (CsvFile.Line line: columnsList) { List<Column> columns = new ArrayList<Column>(); for (int j = 1; j < line.cells.size() && line.cells.get(j).toString().length() > 0; ++j) { String col = line.cells.get(j).trim(); try { columns.add(Column.parse(col)); } catch (Exception e) { // ignore } } Table table = tables.get(SqlUtil.mappedSchema(sourceSchemaMapping, line.cells.get(0))); if (table != null) { table.setColumns(columns); } } } // associations File assFile = new File(getAssociationsFile(executionContext)); List<CsvFile.Line> associationList = new ArrayList<CsvFile.Line>(new CsvFile(openModelFile(assFile, executionContext), null, assFile.getPath(), assocFilter).getLines()); if (additionalAssociationsFile != null) { associationList.addAll(new CsvFile(new File(additionalAssociationsFile)).getLines()); } for (CsvFile.Line line: associationList) { String location = line.location; try { String associationLoadFailedMessage = "Unable to load association from " + line.cells.get(0) + " to " + line.cells.get(1) + " on " + line.cells.get(4) + " because: "; Table tableA = (Table) tables.get(SqlUtil.mappedSchema(sourceSchemaMapping, line.cells.get(0))); if (tableA == null) { continue; // throw new RuntimeException(associationLoadFailedMessage + "Table '" + line.cells.get(0) + "' not found"); } Table tableB = (Table) tables.get(SqlUtil.mappedSchema(sourceSchemaMapping, line.cells.get(1))); if (tableB == null) { continue; // throw new RuntimeException(associationLoadFailedMessage + "Table '" + line.cells.get(1) + "' not found"); } boolean insertSourceBeforeDestination = "A".equalsIgnoreCase(line.cells.get(2)); boolean insertDestinationBeforeSource = "B".equalsIgnoreCase(line.cells.get(2)); Cardinality cardinality = Cardinality.parse(line.cells.get(3).trim()); if (cardinality == null) { cardinality = Cardinality.MANY_TO_MANY; } String joinCondition = line.cells.get(4); String name = line.cells.get(5); if ("".equals(name)) { name = null; } if (name == null) { throw new RuntimeException(associationLoadFailedMessage + "Association name missing (column 6 is empty, each association must have an unique name)"); } String author = line.cells.get(6); Association associationA = new Association(tableA, tableB, insertSourceBeforeDestination, insertDestinationBeforeSource, joinCondition, this, false, cardinality, author); Association associationB = new Association(tableB, tableA, insertDestinationBeforeSource, insertSourceBeforeDestination, joinCondition, this, true, cardinality.reverse(), author); associationA.reversalAssociation = associationB; associationB.reversalAssociation = associationA; tableA.associations.add(associationA); tableB.associations.add(associationB); if (name != null) { if (namedAssociations.put(name, associationA) != null) { throw new RuntimeException("duplicate association name: " + name); } associationA.setName(name); name = "inverse-" + name; if (namedAssociations.put(name, associationB) != null) { throw new RuntimeException("duplicate association name: " + name); } associationB.setName(name); } } catch (Exception e) { throw new RuntimeException(location + ": " + e.getMessage(), e); } } initDisplayNames(); initTableOrdinals(); // model name File nameFile = new File(getModelNameFile(executionContext)); name = DEFAULT_NAME; lastModified = null; try { lastModified = nameFile.lastModified(); if (nameFile.exists()) { List<CsvFile.Line> nameList = new ArrayList<CsvFile.Line>(new CsvFile(nameFile).getLines()); if (nameList.size() > 0) { CsvFile.Line line = nameList.get(0); name = line.cells.get(0); lastModified = Long.parseLong(line.cells.get(1)); } } } catch (Throwable t) { // keep defaults } } catch (IOException e) { _log.error("failed to load data-model " + getDatamodelFolder(executionContext) + File.separator, e); throw e; } } private final List<Table> tableList = new ArrayList<Table>(); private final List<FilterTemplate> filterTemplates = new ArrayList<FilterTemplate>(); /** * Initializes table ordinals. */ private void initTableOrdinals() { for (Table table: getSortedTables()) { table.ordinal = tableList.size(); tableList.add(table); } } /** * Initializes display names. */ private void initDisplayNames() throws IOException { Set<String> unqualifiedNames = new HashSet<String>(); Set<String> nonUniqueUnqualifiedNames = new HashSet<String>(); for (Table table: getTables()) { String uName = table.getUnqualifiedName(); if (unqualifiedNames.contains(uName)) { nonUniqueUnqualifiedNames.add(uName); } else { unqualifiedNames.add(uName); } } for (Table table: getTables()) { String uName = table.getUnqualifiedName(); if (uName != null && uName.length() > 0) { char fc = uName.charAt(0); if (!Character.isLetterOrDigit(fc) && fc != '_') { String fcStr = Character.toString(fc); if (uName.startsWith(fcStr) && uName.endsWith(fcStr)) { uName = uName.substring(1, uName.length() -1); } } } String schema = table.getSchema(null); String displayName; if (nonUniqueUnqualifiedNames.contains(uName) && schema != null) { displayName = uName + " (" + schema + ")"; } else { displayName = uName; } this.displayName.put(table, displayName); tablesByDisplayName.put(displayName, table); } Map<String, String> userDefinedDisplayNames = new TreeMap<String, String>(); File dnFile = new File(DataModel.getDisplayNamesFile(executionContext)); if (dnFile.exists()) { for (CsvFile.Line dnl: new CsvFile(dnFile).getLines()) { userDefinedDisplayNames.put(dnl.cells.get(0), dnl.cells.get(1)); } } for (Map.Entry<String, String> e: userDefinedDisplayNames.entrySet()) { Table table = getTable(e.getKey()); if (table != null && !tablesByDisplayName.containsKey(e.getValue())) { String displayName = getDisplayName(table); this.displayName.remove(table); if (displayName != null) { tablesByDisplayName.remove(displayName); } this.displayName.put(table, e.getValue()); tablesByDisplayName.put(e.getValue(), table); } } } /** * Gets the primary-key to be used for the entity-table. * * @param session for null value guessing * @return the universal primary key */ PrimaryKey getUniversalPrimaryKey(Session session) { return primaryKeyFactory.getUniversalPrimaryKey(session); } /** * Gets the primary-key to be used for the entity-table. * * @return the universal primary key */ PrimaryKey getUniversalPrimaryKey() { return getUniversalPrimaryKey(null); } /** * Gets the restriction model. * * @return the restriction model */ public RestrictionModel getRestrictionModel() { return restrictionModel; } /** * Sets the restriction model. * * @param restrictionModel the restriction model */ public void setRestrictionModel(RestrictionModel restrictionModel) { this.restrictionModel = restrictionModel; ++version; } /** * Gets all independent tables * (i.e. tables which don't depend on other tables in the set) * of a given table-set. * * @param tableSet the table-set * @return the sub-set of independent tables of the table-set */ public Set<Table> getIndependentTables(Set<Table> tableSet) { return getIndependentTables(tableSet, null); } /** * Gets all independent tables * (i.e. tables which don't depend on other tables in the set) * of a given table-set. * * @param tableSet the table-set * @param associations the associations to consider, <code>null</code> for all associations * @return the sub-set of independent tables of the table-set */ public Set<Table> getIndependentTables(Set<Table> tableSet, Set<Association> associations) { Set<Table> independentTables = new HashSet<Table>(); for (Table table: tableSet) { boolean depends = false; for (Association a: table.associations) { if (associations == null || associations.contains(a)) { if (tableSet.contains(a.destination)) { if (a.getJoinCondition() != null) { if (a.isInsertDestinationBeforeSource()) { depends = true; break; } } } } } if (!depends) { independentTables.add(table); } } return independentTables; } /** * Transposes the data-model. */ public void transpose() { if (getRestrictionModel() != null) { getRestrictionModel().transpose(); } ++version; } /** * Stringifies the data model. */ public String toString() { List<Table> sortedTables; sortedTables = getSortedTables(); StringBuffer str = new StringBuffer(); if (restrictionModel != null) { str.append("restricted by: " + restrictionModel + "\n"); } for (Table table: sortedTables) { str.append(table); if (printClosures) { str.append(" closure ="); str.append(new PrintUtil().tableSetAsString(table.closure(true)) + "\n\n"); } } return str.toString(); } /** * Gets list of tables sorted by name. * * @return list of tables sorted by name */ public List<Table> getSortedTables() { List<Table> sortedTables; sortedTables = new ArrayList<Table>(getTables()); Collections.sort(sortedTables, new Comparator<Table>() { public int compare(Table o1, Table o2) { return o1.getName().compareTo(o2.getName()); } }); return sortedTables; } /** * Printing-mode. */ public static boolean printClosures = false; /** * Normalizes a set of tables. * * @param tables set of tables * @return set of all tables from this model for which a table with same name exists in <code>tables</code> */ public Set<Table> normalize(Set<Table> tables) { Set<Table> result = new HashSet<Table>(); for (Table table: tables) { result.add(getTable(table.getName())); } return result; } /** * Assigns a unique ID to each association. */ public void assignAssociationIDs() { int n = 1; for (Map.Entry<String, Association> e: namedAssociations.entrySet()) { e.getValue().id = n++; } } /** * Gets export modus, SQL or XML. (GUI support). */ public String getExportModus() { return exportModus; } /** * Sets export modus, SQL or XML. (GUI support). */ public void setExportModus(String modus) { exportModus = modus; ++version; } /** * Gets XML settings for exportation into XML files. */ public XmlSettings getXmlSettings() { return xmlSettings; } /** * Sets XML settings for exportation into XML files. */ public void setXmlSettings(XmlSettings xmlSettings) { this.xmlSettings = xmlSettings; ++version; } /** * Gets internal version number. Incremented on each modification. * * @return internal version number. Incremented on each modification. */ public long getVersion() { return version; } /** * Thrown if a table has no primary key. */ public static class NoPrimaryKeyException extends RuntimeException { private static final long serialVersionUID = 4523935351640139649L; public final Table table; public NoPrimaryKeyException(Table table) { super("Table '" + table.getName() + "' has no primary key"); this.table = table; } } /** * Checks whether all tables in the closure of a given subject have primary keys. * * @param subject the subject * @throws NoPrimaryKeyException if a table has no primary key */ public void checkForPrimaryKey(Set<Table> subjects, boolean forDeletion) throws NoPrimaryKeyException { Set<Table> checked = new HashSet<Table>(); for (Table subject: subjects) { Set<Table> toCheck = new HashSet<Table>(subject.closure(checked, true)); if (forDeletion) { Set<Table> border = new HashSet<Table>(); for (Table table: toCheck) { for (Association a: table.associations) { if (!a.reversalAssociation.isIgnored()) { border.add(a.destination); } } } toCheck.addAll(border); } for (Table table: toCheck) { if (table.primaryKey.getColumns().isEmpty()) { throw new NoPrimaryKeyException(table); } } checked.addAll(toCheck); } } /** * Gets all parameters which occur in subject condition, association restrictions or XML templates. * * @param subjectCondition the subject condition * @return all parameters which occur in subject condition, association restrictions or XML templates */ public SortedSet<String> getParameters(String subjectCondition, List<ExtractionModel.AdditionalSubject> additionalSubjects) { SortedSet<String> parameters = new TreeSet<String>(); ParameterHandler.collectParameter(subjectCondition, parameters); if (additionalSubjects != null) { for (AdditionalSubject as: additionalSubjects) { ParameterHandler.collectParameter(as.getCondition(), parameters); } } for (Association a: namedAssociations.values()) { String r = a.getRestrictionCondition(); if (r != null) { ParameterHandler.collectParameter(r, parameters); } } for (Table t: getTables()) { String r = t.getXmlTemplate(); if (r != null) { ParameterHandler.collectParameter(r, parameters); } for (Column c: t.getColumns()) { if (c.getFilterExpression() != null) { ParameterHandler.collectParameter(c.getFilterExpression(), parameters); } } } return parameters; } private static InputStream openModelFile(File file, ExecutionContext executionContext) { try { URL dataModelURL = executionContext.getDataModelURL(); URI uri = dataModelURL.toURI(); URI resolved = new URI(uri.toString() + file.getName()); // uri.resolve(file.getName()); return resolved.toURL().openStream(); } catch (MalformedURLException e) { throw new RuntimeException(e); } catch (IOException e) { return null; } catch (URISyntaxException e) { throw new RuntimeException(e); } } /** * Gets {@link #getLastModified()} as String. * * @return {@link #getLastModified()} as String */ public String getLastModifiedAsString() { try { return SimpleDateFormat.getDateTimeInstance(SimpleDateFormat.MEDIUM, SimpleDateFormat.MEDIUM).format(new Date(getLastModified())); } catch (Throwable t) { return ""; } } /** * Saves the data model. * * @param file the file name * @param stable * @param stable the subject table * @param subjectCondition * @param scriptFormat * @param positions table positions or <code>null</code> * @param additionalSubjects */ public void save(String file, Table stable, String subjectCondition, ScriptFormat scriptFormat, List<RestrictionDefinition> restrictionDefinitions, Map<String, Map<String, double[]>> positions, List<AdditionalSubject> additionalSubjects, String currentModelSubfolder) throws FileNotFoundException { File extractionModel = new File(file); PrintWriter out = new PrintWriter(extractionModel); out.println("# subject; condition"); out.println(CsvFile.encodeCell("" + stable.getName()) + "; " + CsvFile.encodeCell(subjectCondition)); saveRestrictions(out, restrictionDefinitions); saveXmlMapping(out); out.println(); out.println(CsvFile.BLOCK_INDICATOR + "datamodelfolder"); if (currentModelSubfolder != null) { out.println(currentModelSubfolder); } out.println(); out.println(CsvFile.BLOCK_INDICATOR + "additional subjects"); for (AdditionalSubject as: additionalSubjects) { out.println(CsvFile.encodeCell("" + as.getSubject().getName()) + "; " + CsvFile.encodeCell(as.getCondition()) + ";"); } out.println(); out.println(CsvFile.BLOCK_INDICATOR + "export modus"); out.println(scriptFormat); out.println(); out.println(CsvFile.BLOCK_INDICATOR + "xml settings"); out.println(CsvFile.encodeCell(getXmlSettings().datePattern) + ";" + CsvFile.encodeCell(getXmlSettings().timestampPattern) + ";" + CsvFile.encodeCell(getXmlSettings().rootTag)); out.println(CsvFile.BLOCK_INDICATOR + "xml column mapping"); for (Table table: getTables()) { String xmlMapping = table.getXmlTemplate(); if (xmlMapping != null) { out.println(CsvFile.encodeCell(table.getName()) + "; " + CsvFile.encodeCell(xmlMapping)); } } out.println(CsvFile.BLOCK_INDICATOR + "upserts"); for (Table table: getTables()) { if (table.upsert != null) { out.println(CsvFile.encodeCell(table.getName()) + "; " + CsvFile.encodeCell(table.upsert.toString())); } } out.println(CsvFile.BLOCK_INDICATOR + "exclude from deletion"); for (Table table: getTables()) { if (table.excludeFromDeletion != null) { out.println(CsvFile.encodeCell(table.getName()) + "; " + CsvFile.encodeCell(table.excludeFromDeletion.toString())); } } saveFilters(out); saveFilterTemplates(out); out.println(); if (positions == null) { LayoutStorage.store(out); } else { LayoutStorage.store(out, positions); } out.println(); out.println(CsvFile.BLOCK_INDICATOR + "known"); for (Association a: namedAssociations.values()) { if (!a.reversed) { out.println(CsvFile.encodeCell(a.getName())); } } out.println(); out.println(CsvFile.BLOCK_INDICATOR + "version"); out.println(JailerVersion.VERSION); out.close(); } /** * Saves xml mappings. * * @param out to save xml mappings into */ private void saveXmlMapping(PrintWriter out) { out.println(); out.println(CsvFile.BLOCK_INDICATOR + "xml-mapping"); for (Table table: getTables()) { for (Association a: table.associations) { String name = a.getName(); String tag = a.getAggregationTagName(); String aggregation = a.getAggregationSchema().name(); out.println(CsvFile.encodeCell(name) + ";" + CsvFile.encodeCell(tag) + ";" + CsvFile.encodeCell(aggregation)); } } } /** * Saves restrictions only. * * @param out to save restrictions into * @param restrictionDefinitions */ private void saveRestrictions(PrintWriter out, List<RestrictionDefinition> restrictionDefinitions) { out.println(); out.println("# association; ; restriction-condition"); for (RestrictionDefinition rd: restrictionDefinitions) { String condition = rd.isIgnored? "ignore" : rd.condition; if (rd.name == null || rd.name.trim().length() == 0) { out.println(CsvFile.encodeCell(rd.from.getName()) + "; " + CsvFile.encodeCell(rd.to.getName()) + "; " + CsvFile.encodeCell(condition)); } else { out.println(CsvFile.encodeCell(rd.name) + "; ; " + CsvFile.encodeCell(condition)); } } } /** * Saves restrictions only. * * @param file to save restrictions into */ public void saveRestrictions(File file, List<RestrictionDefinition> restrictionDefinitions) throws FileNotFoundException { PrintWriter out = new PrintWriter(file); saveRestrictions(out, restrictionDefinitions); out.close(); } /** * Saves filters. * * @param out to save filters into */ private void saveFilters(PrintWriter out) { out.println(); out.println(CsvFile.BLOCK_INDICATOR + "filters"); for (Table table: getTables()) { for (Column c: table.getColumns()) { if (c.getFilter() != null && !c.getFilter().isDerived()) { out.println(CsvFile.encodeCell(table.getName()) + ";" + CsvFile.encodeCell(c.name) + ";" + CsvFile.encodeCell(c.getFilter().getExpression()) + ";" + CsvFile.encodeCell(c.getFilter().isApplyAtExport()? "Export" : "Import") + ";" + CsvFile.encodeCell(c.getFilter().getType() == null? "" : c.getFilter().getType())); } } } } /** * Saves filter templates. * * @param out to save filters into */ private void saveFilterTemplates(PrintWriter out) { out.println(); out.println(CsvFile.BLOCK_INDICATOR + "filter templates"); for (FilterTemplate template: getFilterTemplates()) { out.println("T;" + CsvFile.encodeCell(template.getName()) + ";" + CsvFile.encodeCell(template.getExpression()) + ";" + CsvFile.encodeCell(template.isEnabled()? "enabled" : "disabled") + ";" + CsvFile.encodeCell(template.isApplyAtExport()? "Export" : "Import") + ";" + CsvFile.encodeCell(template.getType() == null? "" : template.getType()) + ";"); for (Clause clause: template.getClauses()) { out.println("C;" + CsvFile.encodeCell(clause.getSubject().name()) + ";" + CsvFile.encodeCell(clause.getPredicate().name()) + ";" + CsvFile.encodeCell(clause.getObject()) + ";"); } } } /** * Gets table by {@link Table#getOrdinal()}. * * @param ordinal the ordinal * @return the table */ public Table getTableByOrdinal(int ordinal) { return tableList.get(ordinal); } /** * Gets the {@link FilterTemplate}s ordered by priority. * * @return template list */ public List<FilterTemplate> getFilterTemplates() { return filterTemplates; } private Map<Association, Map<Column, Column>> sToDMaps = new HashMap<Association, Map<Column,Column>>(); /** * Removes all derived filters and renews them. */ public void deriveFilters() { sToDMaps.clear(); for (Table table: getTables()) { for (Column column: table.getColumns()) { Filter filter = column.getFilter(); if (filter != null && filter.isDerived()) { column.setFilter(null); } } } Set<String> pkNames = new HashSet<String>(); for (Table table: getTables()) { pkNames.clear(); for (Column column: table.primaryKey.getColumns()) { pkNames.add(column.name); } for (Column column: table.getColumns()) { if (pkNames.contains(column.name)) { Filter filter = column.getFilter(); if (filter != null && !filter.isDerived()) { List<String> aTo = new ArrayList<String>(); deriveFilter(table, column, filter, new PKColumnFilterSource(table, column), aTo, null); if (!aTo.isEmpty()) { Collections.sort(aTo); filter.setAppliedTo(aTo); } } } } } // apply templates for (FilterTemplate template: getFilterTemplates()) { if (template.isEnabled()) { for (Table table: getTables()) { for (Column column: table.getColumns()) { if (column.getFilter() == null && template.matches(table, column)) { Filter filter = new Filter(template.getExpression(), template.getType(), true, template); filter.setApplyAtExport(template.isApplyAtExport()); column.setFilter(filter); List<String> aTo = new ArrayList<String>(); deriveFilter(table, column, filter, new PKColumnFilterSource(table, column), aTo, template); if (!aTo.isEmpty()) { Collections.sort(aTo); filter.setAppliedTo(aTo); } } } } } } sToDMaps.clear(); } private void deriveFilter(Table table, Column column, Filter filter, FilterSource filterSource, List<String> aTo, FilterSource overwriteForSource) { for (Association association: table.associations) { if (association.isInsertSourceBeforeDestination()) { Map<Column, Column> sToDMap = sToDMaps.get(association); if (sToDMap == null) { sToDMap = association.createSourceToDestinationKeyMapping(); sToDMaps.put(association, sToDMap); } Column destColumn = sToDMap.get(column); if (destColumn != null && (destColumn.getFilter() == null || overwriteForSource != null && destColumn.getFilter().getFilterSource() == overwriteForSource)) { Filter newFilter = new Filter(filter.getExpression(), filter.getType(), true, filterSource); newFilter.setApplyAtExport(filter.isApplyAtExport()); destColumn.setFilter(newFilter); aTo.add(association.destination.getName() + "." + destColumn.name); deriveFilter(association.destination, destColumn, filter, filterSource, aTo, overwriteForSource); } } } } }