/** * OpenSpotLight - Open Source IT Governance Platform * * Copyright (c) 2009, CARAVELATECH CONSULTORIA E TECNOLOGIA EM INFORMATICA LTDA * or third-party contributors as indicated by the @author tags or express * copyright attribution statements applied by the authors. All third-party * contributions are distributed under license by CARAVELATECH CONSULTORIA E * TECNOLOGIA EM INFORMATICA LTDA. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA * *********************************************************************** * OpenSpotLight - Plataforma de Governança de TI de Código Aberto * * Direitos Autorais Reservados (c) 2009, CARAVELATECH CONSULTORIA E TECNOLOGIA * EM INFORMATICA LTDA ou como contribuidores terceiros indicados pela etiqueta * @author ou por expressa atribuição de direito autoral declarada e atribuída pelo autor. * Todas as contribuições de terceiros estão distribuídas sob licença da * CARAVELATECH CONSULTORIA E TECNOLOGIA EM INFORMATICA LTDA. * * Este programa é software livre; você pode redistribuí-lo e/ou modificá-lo sob os * termos da Licença Pública Geral Menor do GNU conforme publicada pela Free Software * Foundation. * * Este programa é distribuído na expectativa de que seja útil, porém, SEM NENHUMA * GARANTIA; nem mesmo a garantia implícita de COMERCIABILIDADE OU ADEQUAÇÃO A UMA * FINALIDADE ESPECÍFICA. Consulte a Licença Pública Geral Menor do GNU para mais detalhes. * * Você deve ter recebido uma cópia da Licença Pública Geral Menor do GNU junto com este * programa; se não, escreva para: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package org.openspotlight.federation.finder; import static org.openspotlight.common.util.Arrays.of; import static org.openspotlight.common.util.Exceptions.logAndReturnNew; import static org.openspotlight.common.util.PatternMatcher.isMatchingWithoutCaseSentitiveness; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; import java.text.MessageFormat; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.openspotlight.common.exception.ConfigurationException; import org.openspotlight.common.util.Assertions; import org.openspotlight.common.util.SLCollections; import org.openspotlight.common.util.Strings; import org.openspotlight.domain.ArtifactSource; import org.openspotlight.domain.DbArtifactSource; import org.openspotlight.federation.domain.artifact.Artifact; import org.openspotlight.federation.domain.artifact.ChangeType; import org.openspotlight.federation.domain.artifact.db.Column; import org.openspotlight.federation.domain.artifact.db.ColumnType; import org.openspotlight.federation.domain.artifact.db.ConstraintArtifact; import org.openspotlight.federation.domain.artifact.db.DatabaseCustomArtifact; import org.openspotlight.federation.domain.artifact.db.ForeignKeyConstraintArtifact; import org.openspotlight.federation.domain.artifact.db.NullableSqlType; import org.openspotlight.federation.domain.artifact.db.PrimaryKeyConstraintArtifact; import org.openspotlight.federation.domain.artifact.db.RoutineArtifact; import org.openspotlight.federation.domain.artifact.db.RoutineParameter; import org.openspotlight.federation.domain.artifact.db.RoutineParameterType; import org.openspotlight.federation.domain.artifact.db.RoutineType; import org.openspotlight.federation.domain.artifact.db.TableArtifact; import org.openspotlight.federation.domain.artifact.db.ViewArtifact; public class DatabaseCustomArtifactFinder extends AbstractDatabaseArtifactFinder { protected static class DatabaseCustomArtifactInternalLoader { @SuppressWarnings("boxing") private Map<String, RoutineArtifact> loadRoutineMetadata(final DatabaseMetaData metadata) throws SQLException { final ResultSet rs = metadata.getProcedures(null, null, null); final Map<String, RoutineArtifact> result = new HashMap<String, RoutineArtifact>(rs.getFetchSize()); while (rs.next()) { final String catalog = rs.getString("PROCEDURE_CAT"); //$NON-NLS-1$ final String schema = rs.getString("PROCEDURE_SCHEM"); //$NON-NLS-1$ final String name = rs.getString("PROCEDURE_NAME"); //$NON-NLS-1$ final int type = rs.getInt("PROCEDURE_TYPE"); //$NON-NLS-1$ final RoutineType typeAsEnum = RoutineType.getTypeByInt(type); String description; if (catalog != null) { description = MessageFormat.format("/{0}/{1}/{2}/{3}", //$NON-NLS-1$ schema, typeAsEnum, catalog, name); } else { description = MessageFormat.format("/{0}/{1}/{2}", //$NON-NLS-1$ schema, typeAsEnum, name); } final RoutineArtifact newMetadata = Artifact.createArtifact(RoutineArtifact.class, description, ChangeType.INCLUDED); newMetadata.setCatalogName(catalog); newMetadata.setSchemaName(schema); newMetadata.setArtifactName(name); newMetadata.setType(typeAsEnum); final ResultSet columnsRs = metadata.getProcedureColumns(catalog, schema, name, null); while (columnsRs.next()) { final String columnName = columnsRs.getString("COLUMN_NAME"); //$NON-NLS-1$ final int columnType = columnsRs.getInt("DATA_TYPE"); //$NON-NLS-1$ final int routineType = columnsRs.getInt("COLUMN_TYPE"); //$NON-NLS-1$ final int length = columnsRs.getInt("LENGTH"); //$NON-NLS-1$ final int scale = columnsRs.getInt("SCALE"); //$NON-NLS-1$ final int nullable = columnsRs.getInt("NULLABLE"); //$NON-NLS-1$ final RoutineParameter parameter = new RoutineParameter(); parameter.setName(columnName); parameter.setRoutine(newMetadata); parameter.setColumnSize(length); parameter.setType(ColumnType.getTypeByInt(columnType)); parameter.setNullable(NullableSqlType.getNullableByInt(nullable)); parameter.setDecimalSize(scale); parameter.setParameterType(RoutineParameterType.getTypeByInt(routineType)); newMetadata.getParameters().add(parameter); } result.put(description, newMetadata); } return result; } @SuppressWarnings("boxing") private Map<String, DatabaseCustomArtifact> loadTableMetadata(final DatabaseMetaData metadata) throws SQLException { final ResultSet tableRs = metadata.getTables(null, null, null, of("TABLE", "VIEW")); //$NON-NLS-1$//$NON-NLS-2$ final Map<String, DatabaseCustomArtifact> tableMetadata = new HashMap<String, DatabaseCustomArtifact>(); while (tableRs.next()) { final String catalog = tableRs.getString("TABLE_CAT"); //$NON-NLS-1$ final String schema = tableRs.getString("TABLE_SCHEM"); //$NON-NLS-1$ final String tableName = tableRs.getString("TABLE_NAME"); //$NON-NLS-1$ final String tableType = tableRs.getString("TABLE_TYPE"); //$NON-NLS-1$ String description; if (catalog != null) { description = MessageFormat.format("/{0}/{1}/{2}/{3}", //$NON-NLS-1$ schema, catalog, tableType, tableName); } else { description = MessageFormat.format("/{0}/{1}/{2}", //$NON-NLS-1$ schema, tableType, tableName); } TableArtifact desc = null; if (tableMetadata.containsKey(description) && !description.startsWith(Constraints.FOREIGN_KEY.toString()) && !description.startsWith(Constraints.PRIMARY_KEY.toString())) { desc = (TableArtifact) tableMetadata.get(description); } else { if ("VIEW".equals(tableType)) { //$NON-NLS-1$ desc = Artifact.createArtifact(ViewArtifact.class, description, ChangeType.INCLUDED); } else { desc = Artifact.createArtifact(TableArtifact.class, description, ChangeType.INCLUDED); } } desc.setSchemaName(schema); desc.setCatalogName(catalog); desc.setTableName(tableName); final Map<String, Set<String>> pkMap = new HashMap<String, Set<String>>(); final ResultSet pkRs = metadata.getPrimaryKeys(catalog, schema, tableName); while (pkRs.next()) { final String pkColumn = pkRs.getString("COLUMN_NAME"); final String pkName = pkRs.getString("PK_NAME"); Set<String> set = pkMap.get(pkColumn); if (set == null) { set = new HashSet<String>(); pkMap.put(pkColumn, set); } set.add(pkName); } final ResultSet fkRs = metadata.getExportedKeys(catalog, schema, tableName); while (fkRs.next()) { final String fkName = fkRs.getString("FK_NAME"); final String thatCatalog = fkRs.getString("FKTABLE_CAT"); final String thatSchema = fkRs.getString("FKTABLE_SCHEM"); final String thatTable = fkRs.getString("FKTABLE_NAME"); final String thatColumn = fkRs.getString("FKCOLUMN_NAME"); final String fromCatalog = fkRs.getString("PKTABLE_CAT"); final String fromSchema = fkRs.getString("PKTABLE_SCHEM"); final String fromTable = fkRs.getString("PKTABLE_NAME"); final String fromColumn = fkRs.getString("PKCOLUMN_NAME"); final String keyArtifactName = MessageFormat.format("/{0}/{1}", Constraints.FOREIGN_KEY, fkName); final ForeignKeyConstraintArtifact fk = Artifact.createArtifact(ForeignKeyConstraintArtifact.class, keyArtifactName, ChangeType.INCLUDED); fk.setToCatalogName(thatCatalog); fk.setToColumnName(thatColumn); fk.setToSchemaName(thatSchema); fk.setToTableName(thatTable); fk.setFromCatalogName(fromCatalog); fk.setFromColumnName(fromColumn); fk.setFromSchemaName(fromSchema); fk.setFromTableName(fromTable); fk.setConstraintName(fkName); tableMetadata.put(keyArtifactName, fk); } final ResultSet columnRs = metadata.getColumns(catalog, schema, tableName, null); while (columnRs.next()) { final String columnName = columnRs.getString("COLUMN_NAME"); //$NON-NLS-1$ final ColumnType type = ColumnType.getTypeByInt(columnRs.getInt("DATA_TYPE")); //$NON-NLS-1$ final NullableSqlType nullable = NullableSqlType.getNullableByInt(columnRs.getInt("NULLABLE")); //$NON-NLS-1$ final Integer columnSize = columnRs.getInt("COLUMN_SIZE"); //$NON-NLS-1$ final Integer decimalSize = columnRs.getInt("DECIMAL_DIGITS"); //$NON-NLS-1$ Column column = new Column(); column.setTable(desc); column.setName(columnName); findingColumn: if (desc.getColumns().contains(column)) { for (final Column c: desc.getColumns()) { if (c.equals(column)) { column = c; break findingColumn; } } } final Set<String> pks = pkMap.get(columnName); if (pks != null) { for (final String pkName: pks) { final String pkArtifactName = MessageFormat.format("/{0}/{1}", Constraints.PRIMARY_KEY, pkName); final PrimaryKeyConstraintArtifact pk = Artifact.createArtifact(PrimaryKeyConstraintArtifact.class, pkArtifactName, ChangeType.INCLUDED); pk.setConstraintName(pkName); pk.setColumnName(column.getName()); pk.setTableName(column.getTable().getTableName()); pk.setCatalogName(column.getTable().getCatalogName()); pk.setSchemaName(column.getTable().getSchemaName()); tableMetadata.put(pkArtifactName, pk); } } column.setType(type); column.setNullable(nullable); column.setColumnSize(columnSize); column.setDecimalSize(decimalSize); desc.getColumns().add(column); } tableMetadata.put(description, desc); } return tableMetadata; } public Map<String, DatabaseCustomArtifact> loadDatabaseMetadata(final DatabaseMetaData metadata) throws ConfigurationException { try { final Map<String, DatabaseCustomArtifact> tableMetadata = loadTableMetadata(metadata); final Map<String, RoutineArtifact> routineMetadata = loadRoutineMetadata(metadata); final Map<String, DatabaseCustomArtifact> result = new HashMap<String, DatabaseCustomArtifact>( tableMetadata .size() + routineMetadata .size()); result.putAll(tableMetadata); result.putAll(routineMetadata); return result; } catch (final Exception e) { throw logAndReturnNew(e, ConfigurationException.class); } } } public static enum Constraints { FOREIGN_KEY, PRIMARY_KEY } @SuppressWarnings("unchecked") private final Set<Class<? extends Artifact>> availableTypes = SLCollections .<Class<? extends Artifact>>setOf( ForeignKeyConstraintArtifact.class, PrimaryKeyConstraintArtifact.class, TableArtifact.class, ViewArtifact.class, ConstraintArtifact.class, DatabaseCustomArtifact.class); private final ConcurrentHashMap<ArtifactSource, Map<String, DatabaseCustomArtifact>> resultCache = new ConcurrentHashMap<ArtifactSource, Map<String, DatabaseCustomArtifact>>(); private synchronized Map<String, DatabaseCustomArtifact> getResultFrom(final DbArtifactSource source) throws Exception { Assertions.checkNotEmpty("source.databaseName", source.getDatabaseName()); Assertions.checkNotEmpty("source.serverName", source.getServerName()); Assertions.checkNotEmpty("source.initialLookup", source.getInitialLookup()); Map<String, DatabaseCustomArtifact> result = resultCache.get(source); if (result == null) { final DatabaseCustomArtifactInternalLoader loader = new DatabaseCustomArtifactInternalLoader(); final Connection conn = getConnectionFromSource(source); synchronized (conn) { result = loader.loadDatabaseMetadata(conn.getMetaData()); for (final DatabaseCustomArtifact artifact: result.values()) { artifact.setServerName(source.getServerName()); artifact.setDatabaseName(source.getDatabaseName()); artifact.setDatabaseType(source.getType()); artifact.setUrl(source.getInitialLookup()); } } resultCache.put(source, result); } return result; } @Override protected <A extends Artifact> boolean internalAccept(final ArtifactSource source, final Class<A> type) throws Exception { return source instanceof DbArtifactSource && availableTypes.contains(type); } @Override protected synchronized void internalCloseResources() { super.closeResources(); resultCache.clear(); } @Override protected <A extends Artifact> A internalFindByPath(final Class<A> type, final ArtifactSource source, final String path, final String encoding) throws Exception { final DbArtifactSource dbBundle = (DbArtifactSource) source; final Map<String, DatabaseCustomArtifact> resultMap = getResultFrom(dbBundle); @SuppressWarnings("unchecked") final A metadata = (A) resultMap.get(path); return metadata; } @Override protected Set<Class<? extends Artifact>> internalGetAvailableTypes() throws Exception { return availableTypes; } @Override protected boolean internalIsTypeSupported(final Class<? extends Artifact> type) throws Exception { return DatabaseCustomArtifact.class.isAssignableFrom(type); } @Override protected <A extends Artifact> Set<String> internalRetrieveOriginalNames(final Class<A> type, final ArtifactSource source, final String initialPath) throws Exception { String pathToMatch; if (Strings.isEmpty(initialPath)) { pathToMatch = "*"; } else { if (!initialPath.endsWith("*")) { pathToMatch = initialPath + "*"; } else { pathToMatch = initialPath; } if (!initialPath.startsWith("*")) { pathToMatch = "*" + initialPath; } else { pathToMatch = initialPath; } if (!pathToMatch.endsWith("**")) { pathToMatch = pathToMatch + "*"; } if (!pathToMatch.startsWith("**")) { pathToMatch = "*" + pathToMatch; } } final Set<String> artifactNames = new HashSet<String>(); final DbArtifactSource dbBundle = (DbArtifactSource) source; final Map<String, DatabaseCustomArtifact> result = getResultFrom(dbBundle); for (final Map.Entry<String, DatabaseCustomArtifact> entry: result.entrySet()) { if (isMatchingWithoutCaseSentitiveness(entry.getKey(), pathToMatch)) { artifactNames.add(entry.getKey()); } } return artifactNames; } }