001package fr.ifremer.adagio.synchro.meta;
002
003/*
004 * #%L
005 * Tutti :: Persistence API
006 * $Id: TuttiEntities.java 1578 2014-02-07 15:31:18Z tchemit $
007 * $HeadURL: http://svn.forge.codelutin.com/svn/tutti/trunk/tutti-persistence/src/main/java/fr/ifremer/tutti/persistence/entities/TuttiEntities.java $
008 * %%
009 * Copyright (C) 2012 Ifremer
010 * %%
011 * This program is free software: you can redistribute it and/or modify
012 * it under the terms of the GNU Affero General Public License as published by
013 * the Free Software Foundation, either version 3 of the License, or
014 * (at your option) any later version.
015 * 
016 * This program is distributed in the hope that it will be useful,
017 * but WITHOUT ANY WARRANTY; without even the implied warranty of
018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
019 * GNU General Public License for more details.
020 * 
021 * You should have received a copy of the GNU Affero General Public License
022 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
023 * #L%
024 */
025
026import java.sql.Types;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030
031import org.apache.commons.collections.CollectionUtils;
032
033import com.google.common.base.Preconditions;
034import com.google.common.base.Predicate;
035import com.google.common.collect.Lists;
036import com.google.common.collect.Sets;
037
038import fr.ifremer.adagio.synchro.SynchroTechnicalException;
039
040/**
041 * Useful method around DAO and entities.
042 * 
043 * @author Benoit Lavenier <benoit.lavenier@e-is.pro>
044 * @since 3.5.4
045 */
046public class SynchroMetadataUtils {
047
048    public static final List<String> ORACLE_EXCLUDE_TABLE_PATTERNS = Lists.newArrayList(
049            "BIN%", // Trash Oracle
050            "MDR%" // Table metadata spatial
051    );
052
053    protected SynchroMetadataUtils() {
054        // helper class does not instantiate
055    }
056
057    public static Predicate<String> newAllTablesOraclePredicate() {
058        return newTablesOraclePredicate(null, null);
059    }
060
061    public static Predicate<String> newTablesOraclePredicate(Set<String> excludes, final Set<String> includes) {
062        Set<String> excludesPatterns = Sets.newHashSet(ORACLE_EXCLUDE_TABLE_PATTERNS);
063
064        if (CollectionUtils.isNotEmpty(excludes)) {
065            excludesPatterns.addAll(excludes);
066        }
067
068        return newTablePredicate(excludesPatterns, includes);
069    }
070
071    public static Predicate<String> newTablePredicate(final Set<String> excludes, final Set<String> includes) {
072        // If no filter
073        if (CollectionUtils.isEmpty(excludes) && CollectionUtils.isEmpty(includes)) {
074            return null;
075        }
076
077        return new Predicate<String>() {
078            public boolean apply(String tableName) {
079
080                if (CollectionUtils.isNotEmpty(excludes)) {
081                    for (String excludePattern : excludes) {
082                        if (tableName.matches(excludePattern.replaceAll("%", ".*"))) {
083                            return false;
084                        }
085                    }
086                }
087                if (CollectionUtils.isEmpty(includes)) {
088                    return true;
089                }
090                for (String includePattern : includes) {
091                    if (tableName.matches(includePattern.replaceAll("%", ".*"))) {
092                        return true;
093                    }
094                }
095                return false;
096            }
097        };
098    }
099
100    public static Predicate<SynchroColumnMetadata> newExcludeColumnPredicate(final Map<String, Set<String>> excludeColumnNamesMap) {
101        Preconditions.checkNotNull(excludeColumnNamesMap);
102        Preconditions.checkArgument(!excludeColumnNamesMap.isEmpty());
103
104        return new Predicate<SynchroColumnMetadata>() {
105            public boolean apply(SynchroColumnMetadata input) {
106                Set<String> excludeColumnNames = excludeColumnNamesMap.get(input.getTableName());
107                return CollectionUtils.isEmpty(excludeColumnNames) || !excludeColumnNames.contains(input.getName().toLowerCase());
108            }
109        };
110    }
111
112    /**
113     * Check if types are compatible, and return a DataRetrievalFailureException if not compatible.
114     * 
115     * @param tableName
116     * @param internalColumn
117     * @param externalColumn
118     */
119    public static void checkType(String tableName, SynchroColumnMetadata internalColumn, SynchroColumnMetadata externalColumn) {
120
121        // If numeric
122        if (isNumericType(internalColumn) && isNumericType(externalColumn)) {
123            int internalColumnSize = internalColumn.getColumnSize();
124            int externalColumnSize = externalColumn.getColumnSize();
125            if (internalColumnSize > 0 && externalColumnSize > 0 && internalColumnSize < externalColumnSize) {
126                throw new SynchroTechnicalException(String.format("Incompatible column type of table / column: %s / %s", tableName,
127                        internalColumn.getName()));
128            }
129            int internalDecimalDigits = internalColumn.getDecimalDigits();
130            int externalDecimalDigits = externalColumn.getDecimalDigits();
131            if (internalDecimalDigits > 0 && externalDecimalDigits > 0 && internalDecimalDigits < externalDecimalDigits) {
132                throw new SynchroTechnicalException(String.format("Incompatible column type of table / column: %s / %s", tableName,
133                        internalColumn.getName()));
134            }
135        }
136
137        // If Date
138        else if (isDateType(internalColumn) && isDateType(externalColumn)) {
139            // OK
140        }
141
142        // If Boolean
143        else if (isBooleanType(internalColumn) && isBooleanType(externalColumn)) {
144            // OK
145        }
146
147        // Else : compare type code and name
148        else {
149            String internalColumnTypeName = internalColumn.getTypeName();
150            String externalColumnTypeName = externalColumn.getTypeName();
151            int internalColumnTypeCode = internalColumn.getTypeCode();
152            int externalColumnTypeCode = externalColumn.getTypeCode();
153
154            if (internalColumnTypeCode != externalColumnTypeCode && internalColumnTypeName.equals(externalColumnTypeName) == false) {
155                throw new SynchroTechnicalException(String.format("Incompatible column type of table / column: %s / %s", tableName,
156                        internalColumn.getName()));
157            }
158        }
159    }
160
161    protected static boolean isNumericType(SynchroColumnMetadata column) {
162        int typeCode = column.getTypeCode();
163        if (typeCode == Types.BIGINT || typeCode == Types.INTEGER || typeCode == Types.NUMERIC
164                || typeCode == Types.DECIMAL || typeCode == Types.FLOAT || typeCode == Types.REAL
165                || typeCode == Types.SMALLINT || typeCode == Types.TINYINT || typeCode == Types.DOUBLE) {
166            return true;
167        }
168
169        String columnTypeName = column.getTypeName();
170        return columnTypeName.equals("NUMBER") || columnTypeName.equals("INTEGER") || columnTypeName.equals("SMALLINT");
171    }
172
173    protected static boolean isDateType(SynchroColumnMetadata column) {
174        String columnTypeName = column.getTypeName();
175        return columnTypeName.equals("TIMESTAMP") || columnTypeName.equals("DATE");
176    }
177
178    protected static boolean isBooleanType(SynchroColumnMetadata column) {
179        String columnTypeName = column.getTypeName();
180        int columnSize = column.getColumnSize();
181        return columnTypeName.equals("BOOLEAN") ||
182                (columnTypeName.equals("NUMBER") && columnSize == 1);
183    }
184
185}