/* * Copyright (c) 2013-2015 Josef Hardi <josef.hardi@gmail.com> * * 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 com.obidea.semantika.knowledgebase.processor; import java.net.URI; import java.util.ArrayList; import java.util.List; import java.util.Set; import org.slf4j.Logger; import com.obidea.semantika.database.IDatabase; import com.obidea.semantika.database.IDatabaseMetadata; import com.obidea.semantika.database.NamingUtils; import com.obidea.semantika.database.base.IColumn; import com.obidea.semantika.database.base.IColumnReference; import com.obidea.semantika.database.base.IForeignKey; import com.obidea.semantika.database.base.ITable; import com.obidea.semantika.database.datatype.SqlTypeToXmlType; import com.obidea.semantika.expression.ExpressionObjectFactory; import com.obidea.semantika.expression.base.IAtom; import com.obidea.semantika.expression.base.IRule; import com.obidea.semantika.expression.base.ITerm; import com.obidea.semantika.expression.base.IVariable; import com.obidea.semantika.knowledgebase.TermSubstitutionBinding; import com.obidea.semantika.knowledgebase.UnificationException; import com.obidea.semantika.knowledgebase.model.IKnowledgeBase; import com.obidea.semantika.mapping.MutableMappingSet; import com.obidea.semantika.mapping.base.IMapping; import com.obidea.semantika.util.CollectionUtils; import com.obidea.semantika.util.LogUtils; import com.obidea.semantika.util.MultiMap; public class ReferentialIntegrityProcessor implements IKnowledgeBaseProcessor { private List<IRule> mFkRules = new ArrayList<IRule>(); private IDatabase mDatabase; private MutableMappingSet mMappingSet; private IgnoredMappings mIgnoredMappings = new IgnoredMappings(); private static ExpressionObjectFactory sExpressionFactory = ExpressionObjectFactory.getInstance(); private static final Logger LOG = LogUtils.createLogger("semantika.knowledgebase.processor"); //$NON-NLS-1$ public ReferentialIntegrityProcessor() { // NO-OP } private static MutableMappingSet mutableMappingSet(IKnowledgeBase kb) throws KnowledgeBaseProcessorException { if (!(kb.getMappingSet() instanceof MutableMappingSet)) { throw new KnowledgeBaseProcessorException("Optimization requires mutable mapping set object"); //$NON-NLS-1$ } return (MutableMappingSet) kb.getMappingSet(); } @Override public void optimize(IKnowledgeBase kb) throws KnowledgeBaseProcessorException { mIgnoredMappings.clear(); mMappingSet = mutableMappingSet(kb); mDatabase = kb.getDatabase(); createForeignKeyRules(); for (URI signature : mMappingSet.getMappingSignatures()) { Set<IMapping> mappings = mMappingSet.get(signature); if (mappings.size() == 1) { // skip if found a single mapping continue; } doOptimization(signature, mappings); } logIgnoreMappings(); } private void doOptimization(URI signature, Set<IMapping> mappings) { List<IMapping> removeList = new ArrayList<IMapping>(); List<IMapping> mappingList = CollectionUtils.toList(mappings); for (int i = 0; i < mappingList.size(); i++) { for (int j = 0; j < mappingList.size(); j++) { try { IMapping mapping1 = mappingList.get(i); IMapping mapping2 = mappingList.get(j); if (mapping1.equals(mapping2)) { // skip if both mappings are identical continue; } if (MappingContainmentChecker.isContained(mapping1, mapping2, mFkRules)) { removeList.add(mapping2); } } catch (MappingContaimentCheckException e) { String message = String.format("Failed to optimize %s (Reason: %s)", signature, e.getMessage()); mIgnoredMappings.add(message, e.getInvalidMapping()); } } } mMappingSet.removeAll(removeList); } private void createForeignKeyRules() { IDatabaseMetadata md = mDatabase.getMetadata(); Set<IForeignKey> fkeys = md.getForeignKeys(); for (IForeignKey fk : fkeys) { ITable referenceTable = null; ITable sourceTable = null; TermSubstitutionBinding theta = TermSubstitutionBinding.createEmptyBinding(); for (IColumnReference cr : fk.getReferences()) { try { IColumn fkColumn = cr.getForeignKeyColumn(); IColumn pkColumn = cr.getPrimaryKeyColumn(); createSubstitutionBinding(fkColumn, pkColumn, theta); referenceTable = fkColumn.getTableOrigin(); sourceTable = pkColumn.getTableOrigin(); } catch (UnificationException e) { LOG.error("Error while creating foreign key rules"); //$NON-NLS-1$ LOG.error("Detailed cause: {}", e.getMessage()); //$NON-NLS-1$ } } IAtom ruleHead = createAtomFromTable(referenceTable); IAtom ruleBody = createAtomFromTable(sourceTable); ruleBody.apply(theta); mFkRules.add(sExpressionFactory.createRule(ruleHead, CollectionUtils.asList(ruleBody))); } } private static void createSubstitutionBinding(IColumn fkColumn, IColumn pkColumn, TermSubstitutionBinding theta) throws UnificationException { IVariable fkVariable = getVariableFromColumn(fkColumn); IVariable pkVariable = getVariableFromColumn(pkColumn); theta.put(pkVariable, fkVariable); } private IAtom createAtomFromTable(ITable table) { String name = getPredicateNameFromTable(table); List<ITerm> terms = new ArrayList<ITerm>(); for (IColumn column : table.getColumns()) { terms.add(getVariableFromColumn(column)); } return sExpressionFactory.createAtom(name, terms); } private static String getPredicateNameFromTable(ITable table) { return NamingUtils.constructExpressionObjectLabel(table.getSchemaName(), table.getLocalName()); } private static IVariable getVariableFromColumn(IColumn column) { String variableName = NamingUtils.constructExpressionObjectLabel(column.getSchemaName(), column.getTableName(), column.getLocalName()); String datatype = SqlTypeToXmlType.get(column.getSqlType()); return sExpressionFactory.getVariable(variableName, datatype); } @Override public String getName() { return "ReferentialIntegrity processor"; //$NON-NLS-1$ } private void logIgnoreMappings() { if (!mIgnoredMappings.isEmpty()) { MultiMap<String, IMapping> ignoreMap = mIgnoredMappings.asMap(); for (String cause : ignoreMap.keySet()) { LOG.debug(" - {} ({} items)", cause, ignoreMap.get(cause).size()); //$NON-NLS-1$ } } } /** * Utility class to store the ignored mappings when optimizing the mapping set using referential * integrity constraint (i.e., foreign key definition). */ private class IgnoredMappings { private MultiMap<String, IMapping> mIgnoreMap = new MultiMap<String, IMapping>(true); public void add(String cause, IMapping mapping) { mIgnoreMap.put(cause, mapping); } public MultiMap<String, IMapping> asMap() { return mIgnoreMap; } public boolean isEmpty() { return mIgnoreMap.size() == 0; } public void clear() { mIgnoreMap.clear(); } } }