/** * Copyright 2012 Universitat Pompeu Fabra. * * 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 org.onexus.collection.store.sql; import org.onexus.collection.api.Collection; import org.onexus.collection.api.Field; import org.onexus.collection.api.Link; import org.onexus.collection.api.utils.LinkUtils; import org.onexus.collection.store.sql.adapters.SqlAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; public class SqlCollectionDDL { private final static Logger LOGGER = LoggerFactory .getLogger(SqlCollectionDDL.class); public final static char FIELD_SEPARATOR = '.'; private Collection collection; private String tableName; private StringBuilder createTable; private Map<String, ColumnInfo> columnInfos; private SqlDialect sqlDialect; private List<String> createIndex; private List<String> dropIndex; public SqlCollectionDDL(SqlDialect sqlDialect, Collection collection, String tableName) { this.sqlDialect = sqlDialect; this.collection = collection; this.columnInfos = new HashMap<String, ColumnInfo>(); this.tableName = tableName == null ? convertURItoTableName(collection) : tableName; prepareFieldInfoMap(collection); prepareCreateTable(collection); prepareCreateIndex(collection); } private void prepareCreateIndex(Collection collection) { this.createIndex = new ArrayList<String>(); this.dropIndex = new ArrayList<String>(); if (collection.getLinks() != null) { StringBuilder linkSQL, dropIndexSQL; int i = 0; for (Link link : collection.getLinks()) { linkSQL = new StringBuilder(); dropIndexSQL = new StringBuilder(); String indexName = getTableName() + "-" + Integer.toString(i); linkSQL.append("CREATE INDEX `").append(indexName); linkSQL.append("` ON `").append(getTableName()).append("` ("); dropIndexSQL.append("DROP INDEX `").append(indexName).append("`"); Iterator<String> fieldIt = link.getFields().iterator(); while (fieldIt.hasNext()) { String fromFieldId = LinkUtils.getFromFieldName(fieldIt.next()); ColumnInfo indexField = getColumnInfoByFieldName(fromFieldId); if (indexField == null) { throw new UnsupportedOperationException("Impossible to link field '" + fromFieldId + "' at '" + collection.getORI() + "'."); } linkSQL.append("`").append(indexField.getColumnName()).append("`"); if (fieldIt.hasNext()) { linkSQL.append(", "); } } linkSQL.append(")"); this.createIndex.add(linkSQL.toString()); this.dropIndex.add(dropIndexSQL.toString()); i++; } } } private static String convertURItoTableName(Collection collection) { String hashCode = Integer.toHexString(collection.getORI().getProjectUrl().hashCode()); String tableName = removeNonValidChars(collection.getORI().getPath()); // Check that the table name is no longer than 64 characters (the // maximum allowed) int totalLength = tableName.length() + hashCode.length() + 1; if (totalLength > 64) { return tableName.substring(0, tableName.length() - (totalLength - 64)) + "_" + hashCode; } else { return hashCode + "_" + tableName; } } public Collection getCollection() { return collection; } public String getTableName() { return tableName; } public String getCreateTable() { return createTable.toString(); } public String getDropTable() { return "DROP TABLE IF EXISTS `" + tableName + "`"; } public java.util.Collection<ColumnInfo> getColumnInfos() { return columnInfos.values(); } public ColumnInfo getColumnInfo(String columnName) { return columnInfos.get(columnName); } public ColumnInfo getColumnInfoByFieldName(String fieldName) { for (ColumnInfo ci : columnInfos.values()) { if (ci.getField().getId().equals(fieldName)) { return ci; } } return null; } private void prepareFieldInfoMap(Collection collection) { if (collection == null) { return; } for (Field field : collection.getFields()) { ColumnInfo fi = new ColumnInfo(field); columnInfos.put(fi.getColumnName(), fi); } } private void prepareCreateTable(Collection collection) { createTable = new StringBuilder(); createTable.append("CREATE TABLE IF NOT EXISTS `").append(tableName) .append("` ("); Iterator<ColumnInfo> ifi = columnInfos.values().iterator(); while (ifi.hasNext()) { ColumnInfo fi = ifi.next(); createTable.append("`").append(fi.getColumnName()).append("` ") .append(fi.getColumnType()); if (ifi.hasNext()) { createTable.append(", "); } } // Add primary key // TODO Use common collections filters List<String> keys = new ArrayList<String>(); for (Field field : collection.getFields()) { if (field.isPrimaryKey() != null && field.isPrimaryKey()) { keys.add(field.getId()); } } if (!keys.isEmpty()) { createTable.append(", PRIMARY KEY (`"); Iterator<String> keyFields = keys.iterator(); while (keyFields.hasNext()) { String fieldURI = keyFields.next(); String columnName = null; for (ColumnInfo column : columnInfos.values()) { if (column.getField().getId().equals(fieldURI)) { columnName = column.getColumnName(); break; } } if (columnName == null) { String msg = String.format( "Key field '%s' not fount in collection '%s'.", fieldURI, collection); LOGGER.error(msg); throw new RuntimeException(msg); } createTable.append(columnName); if (keyFields.hasNext()) { createTable.append("`, `"); } } createTable.append("`)"); } createTable.append(")"); } private static String removeNonValidChars(String id) { return id.toLowerCase().trim().replaceAll("[^a-z0-9]", "_"); } public List<String> getCreateIndex() { return createIndex; } public List<String> getDropIndex() { return dropIndex; } public class ColumnInfo { private Field field; private String columnName; private String columnType = null; private ColumnInfo(Field field) { this.columnName = removeNonValidChars(field.getId()); this.field = field; this.columnType = field.getProperty("SQL_COLUMN_TYPE"); if (this.columnType == null) { this.columnType = sqlDialect.getColumnType(field.getType()); } if (this.columnType == null) { String msg = "Column type not found for the field: '" + field + "'"; LOGGER.error(msg); throw new RuntimeException(msg); } } public Field getField() { return field; } public String getColumnName() { return columnName; } public String getColumnType() { return columnType; } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("ColumnInfo [field="); builder.append(field); builder.append(", columnName="); builder.append(columnName); builder.append(", columnType="); builder.append(columnType); builder.append("]"); return builder.toString(); } public SqlAdapter getAdapter() { return sqlDialect.getAdapter(field.getType()); } } }