/*
* 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.restrictionmodel;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import net.sf.jailer.ExecutionContext;
import net.sf.jailer.datamodel.Association;
import net.sf.jailer.datamodel.DataModel;
import net.sf.jailer.datamodel.ParameterHandler;
import net.sf.jailer.datamodel.Table;
import net.sf.jailer.util.CsvFile;
import net.sf.jailer.util.SqlUtil;
/**
* Restricts association-definitions in a {@link DataModel}.
*
* @author Ralf Wisser
*/
public class RestrictionModel {
/**
* The data-model.
*/
private final DataModel dataModel;
/**
* Restrictions (in SQL) for associations.
*/
private final Map<Association, String> restriction = new HashMap<Association, String>();
/**
* The name of the restriction-files read.
*/
private List<String> filesRead = new ArrayList<String>();
/**
* Whether the restriction-model is transposed.
*/
private boolean transposed = false;
/**
* The logger.
*/
private static final Logger _log = Logger.getLogger(RestrictionModel.class);
/**
* Constructor.
*
* @param dataModel the data-model
*/
public RestrictionModel(DataModel dataModel, ExecutionContext executionContext) {
this.dataModel = dataModel;
}
/**
* Transposes the restriction-model.
*/
public void transpose() {
transposed = !transposed;
if (dataModel != null) {
dataModel.version++;
}
}
/**
* Is the restriction-model transposed?
*/
public boolean isTransposed() {
return transposed;
}
/**
* "ignore the association" - restriction.
*/
public static final String IGNORE = new String("ignore");
/**
* Gets the restriction (in SQL) for an association.
*
* @param association the association
* @return the restriction.
* <code>null</code> if association is not restricted.
* {@link #IGNORE} if association must be ignored.
*/
public String getRestriction(Association association) {
if (transposed) {
association = association.reversalAssociation;
}
if (!restriction.containsKey(association)) {
return null;
}
String rest = restriction.get(association);
if (rest == null) {
return IGNORE;
}
return rest;
}
/**
* Adds restrictions defined in a restriction-file.
*
* @param parameters apply this parameter-value mapping to all restriction conditions
*/
public void addRestrictionDefinition(URL extractionModelURL, Map<String, String> parameters) throws Exception {
if (dataModel != null) {
dataModel.version++;
}
List<CsvFile.Line> lines = new CsvFile(extractionModelURL.openStream(), null, extractionModelURL.toString(), null).getLines();
int nr = 0;
for (CsvFile.Line line: lines) {
++nr;
if (nr == 1) {
continue;
}
String location = line.location;
if ("".equals(line.cells.get(1))) {
Association association = dataModel.namedAssociations.get(line.cells.get(0));
if (association == null) {
_log.warn(location + ": unknown association '" + line.cells.get(0) + "'");
continue;
}
String condition = line.cells.get(2);
if ("".equals(condition)) {
_log.warn(location + ": missing condition");
continue;
}
addRestriction(null, association, condition, location, parameters);
continue;
}
Table from;
boolean reversFrom = false;
if ("*".equals(line.cells.get(0))) {
from = null;
} else {
if (line.cells.get(0).startsWith("*-")) {
from = dataModel.getTable(line.cells.get(0).substring(2));
reversFrom = true;
} else {
from = dataModel.getTable(line.cells.get(0));
}
if (from == null) {
_log.warn(location + ": unknown table '" + line.cells.get(0) + "'");
continue;
}
}
Table to;
List<Table> excludeTo = new ArrayList<Table>();
if (from != null && "*".equals(line.cells.get(1))) {
to = null;
} else if (line.cells.get(1).startsWith("*-")) {
to = null;
for (String t: line.cells.get(1).substring(2).split(",")) {
t = t.trim();
Table excludeTable = dataModel.getTable(t);
if (excludeTable == null) {
_log.warn(location + ": unknown table '" + t + "'");
continue;
}
excludeTo.add(excludeTable);
}
} else {
to = dataModel.getTable(line.cells.get(1));
if (to == null) {
_log.warn(location + ": unknown table '" + line.cells.get(1) + "'");
continue;
}
}
String condition = line.cells.get(2);
if ("".equals(condition)) {
throw new RuntimeException(location + ": missing condition");
}
if (from == null || reversFrom) {
for (Table table: dataModel.getTables()) {
if (from != null && table.equals(from)) {
continue;
}
for (Association a: table.associations) {
if (a.destination.equals(to)) {
if (!a.isInsertDestinationBeforeSource()) {
addRestriction(table, a, condition, location, parameters);
}
}
}
}
} else if (to == null) {
for (Association a: from.associations) {
if (!a.isInsertDestinationBeforeSource()) {
if (excludeTo == null || !excludeTo.contains(a.destination)) {
addRestriction(from, a, condition, location, parameters);
}
}
}
} else {
for (Association a: from.associations) {
if (a.destination.equals(to)) {
addRestriction(from, a, condition, location, parameters);
}
}
}
}
filesRead.add(extractionModelURL.toString());
}
/**
* Adds a restriction to a association.
*
* @param association the association
* @param condition the restriction-condition
* @param parameters apply this parameter-value mapping to all restriction conditions
* @param location location in CSV-file
*/
public void addRestriction(Table source, Association association, String condition, String location, Map<String, String> parameters) {
addRestriction(source, association, condition, location, false, parameters);
}
/**
* Adds a restriction to a association.
*
* @param association the association
* @param condition the restriction-condition
* @param location location in CSV-file
* @param parameters apply this parameter-value mapping to all restriction conditions
* @param removePreviousRestriction if <code>true</code>, remove any restriction on the association before adding the new one
*/
public void addRestriction(Table source, Association association, String condition, String location, boolean removePreviousRestriction, Map<String, String> parameters) {
if (dataModel != null) {
dataModel.version++;
}
condition = ParameterHandler.assignParameterValues(condition, parameters);
// if (association.isInsertDestinationBeforeSource()) {
// String aName = source == null? association.getName() : (source.getName() + "->" + association.destination.getName());
// throw new RuntimeException(location + ": can't restrict dependency: " + aName + " condition: " + condition);
// }
if ("ignore".equalsIgnoreCase(condition) || "false".equalsIgnoreCase(condition)) {
condition = null;
}
if (condition != null && association.reversed) {
condition = SqlUtil.reversRestrictionCondition(condition);
}
if (removePreviousRestriction && "".equals(condition)) {
restriction.remove(association);
} else if (removePreviousRestriction || !restriction.containsKey(association)) {
restriction.put(association, condition == null? null : "(" + condition + ")");
} else {
String oldCondition = restriction.get(association);
if (oldCondition == null || condition == null) {
condition = null;
} else {
condition = oldCondition + " and (" + condition + ")";
}
restriction.put(association, condition);
}
}
/**
* Stringifies the restriction model.
*/
public String toString() {
return filesRead.toString();
}
}