/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.server.store.format;
import com.foundationdb.ais.model.FullTextIndex;
import com.foundationdb.ais.model.Group;
import com.foundationdb.ais.model.HasStorage;
import com.foundationdb.ais.model.NameGenerator;
import com.foundationdb.ais.model.StorageDescription;
import com.foundationdb.ais.model.TableName;
import com.foundationdb.ais.protobuf.AISProtobuf.Storage;
import com.foundationdb.qp.virtualadapter.VirtualScanFactory;
import com.foundationdb.sql.parser.StorageFormatNode;
import com.foundationdb.server.error.UnsupportedSQLException;
import com.foundationdb.server.service.config.ConfigurationService;
import com.google.protobuf.ExtensionRegistry;
import com.google.protobuf.GeneratedMessage;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/** A registry of mappings between DDL STORAGE_FORMAT clauses and
* Protobuf extension fields and {@link StorageDescription} instances.
*/
public abstract class StorageFormatRegistry
{
private final ConfigurationService configService;
private String defaultIdentifier;
public StorageFormatRegistry(String defaultIdentifier) {
this.configService = null;
this.defaultIdentifier = defaultIdentifier;
}
public StorageFormatRegistry(ConfigurationService configService) {
this.configService = configService;
}
static class Format<T extends StorageDescription> implements Comparable<Format<?>> {
final GeneratedMessage.GeneratedExtension<Storage,?> protobufExtension;
final String sqlIdentifier;
final Class<T> descriptionClass;
final StorageFormat<T> storageFormat;
public Format(GeneratedMessage.GeneratedExtension<Storage,?> protobufExtension, String sqlIdentifier, Class<T> descriptionClass, StorageFormat<T> storageFormat) {
this.protobufExtension = protobufExtension;
this.sqlIdentifier = sqlIdentifier;
this.descriptionClass = descriptionClass;
this.storageFormat = storageFormat;
}
public int compareTo(Format<?> other) {
if (descriptionClass != other.descriptionClass) {
// Do more specific class first.
if (descriptionClass.isAssignableFrom(other.descriptionClass)) {
return +1;
}
else if (other.descriptionClass.isAssignableFrom(descriptionClass)) {
return -1;
}
}
// Do higher field number first.
return Integer.compare(other.protobufExtension.getDescriptor().getNumber(),
protobufExtension.getDescriptor().getNumber());
}
}
private final ExtensionRegistry extensionRegistry = ExtensionRegistry.newInstance();
private final Collection<Format> formatsInOrder = new TreeSet<>();
private final Map<Integer,Format> formatsByField = new TreeMap<>();
private final Map<String,Format<?>> formatsByIdentifier = new TreeMap<>();
private Constructor<? extends StorageDescription> defaultStorageConstructor;
// The VirtualScanFactory itself cannot be serialized, so remember
// it by group name and recover that way. Could remember a unique
// id and actually write that, but sometimes the virtual table AIS
// is actually written to disk.
private final Map<TableName,VirtualScanFactory> virtualScanFactories = new HashMap<>();
public void registerStandardFormats() {
VirtualTableStorageFormat.register(this, virtualScanFactories);
FullTextIndexFileStorageFormat.register(this);
getDefaultDescriptionConstructor();
}
void getDefaultDescriptionConstructor() {
Format<? extends StorageDescription> format = formatsByIdentifier.get(configService.getProperty("fdbsql.default_storage_format"));
defaultIdentifier = configService.getProperty("fdbsql.default_storage_format");
try {
defaultStorageConstructor = format.descriptionClass.getConstructor(HasStorage.class, String.class);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public StorageDescription getDefaultStorageDescription(HasStorage object) {
try {
return defaultStorageConstructor.newInstance(object, defaultIdentifier);
} catch (InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
/** Return the Protbuf extension registry. */
public ExtensionRegistry getExtensionRegistry() {
return extensionRegistry;
}
/** Register a new {@link StorageFormat}.
* @param protobufExtension the extension field that keys use of this format
* @param sqlIdentifier the <code>STORAGE_FORMAT</code> identifier that keys use of this format or <code>null</code>
* @param descriptionClass that specific class used to hold this format
* @param storageFormat the mapping handler
*/
public <T extends StorageDescription> void registerStorageFormat(GeneratedMessage.GeneratedExtension<Storage,?> protobufExtension, String sqlIdentifier, Class<T> descriptionClass, StorageFormat<T> storageFormat) {
int fieldNumber = protobufExtension.getDescriptor().getNumber();
if (formatsByField.containsKey(fieldNumber))
throw new IllegalArgumentException("there is already a StorageFormat registered for field " + fieldNumber);
if ((sqlIdentifier != null) &&
formatsByIdentifier.containsKey(sqlIdentifier))
throw new IllegalArgumentException("there is already a StorageFormat registered for STORAGE_FORMAT " + sqlIdentifier);
if (!isDescriptionClassAllowed(descriptionClass)) {
throw new IllegalArgumentException("description " + descriptionClass + " not allowed for " + getClass().getSimpleName());
}
extensionRegistry.add(protobufExtension);
Format<T> format = new Format<T>(protobufExtension, sqlIdentifier, descriptionClass, storageFormat);
formatsInOrder.add(format);
formatsByField.put(fieldNumber, format);
if (sqlIdentifier != null) {
formatsByIdentifier.put(sqlIdentifier, format);
}
}
/** Could this registry (and its associated store) support this class? */
public boolean isDescriptionClassAllowed(Class<? extends StorageDescription> descriptionClass) {
return (VirtualTableStorageDescription.class.isAssignableFrom(descriptionClass) ||
FullTextIndexFileStorageDescription.class.isAssignableFrom(descriptionClass));
}
public void registerVirtualScanFactory(TableName name, VirtualScanFactory scanFactory) {
virtualScanFactories.put(name, scanFactory);
}
public void unregisterVirtualScanFactory(TableName name) {
virtualScanFactories.remove(name);
}
@SuppressWarnings("unchecked")
public StorageDescription readProtobuf(Storage pbStorage, HasStorage forObject) {
StorageDescription storageDescription = null;
for (Format format : formatsInOrder) {
if (pbStorage.hasExtension(format.protobufExtension)) {
storageDescription = readProtobuf(format, pbStorage, forObject, storageDescription);
}
}
if (!pbStorage.getUnknownFields().asMap().isEmpty()) {
if (storageDescription == null) {
storageDescription = new UnknownStorageDescription(forObject, defaultIdentifier);
}
storageDescription.setUnknownFields(pbStorage.getUnknownFields());
}
return storageDescription;
}
@SuppressWarnings("unchecked")
protected <T extends StorageDescription> T readProtobuf(Format<T> format, Storage pbStorage, HasStorage forObject, StorageDescription storageDescription) {
if ((storageDescription != null) &&
!format.descriptionClass.isInstance(storageDescription)) {
throw new IllegalStateException("incompatible storage format handlers: required " + format.descriptionClass.getName() + " but have " + storageDescription.getClass());
}
return format.storageFormat.readProtobuf(pbStorage, forObject, (T)storageDescription);
}
public StorageDescription parseSQL(StorageFormatNode node, HasStorage forObject) {
Format<?> format = formatsByIdentifier.get(node.getFormat());
if (format == null) {
throw new UnsupportedSQLException("", node);
}
return format.storageFormat.parseSQL(node, forObject);
}
public void finishStorageDescription(HasStorage object, NameGenerator nameGenerator) {
if (object.getStorageDescription() == null) {
if (object instanceof Group) {
VirtualScanFactory factory = virtualScanFactories.get(((Group)object).getName());
if (factory != null) {
object.setStorageDescription(new VirtualTableStorageDescription(object, factory, VirtualTableStorageFormat.identifier));
}
else {
object.setStorageDescription(getDefaultStorageDescription(object));
}
}
else if (object instanceof FullTextIndex) {
File path = new File(nameGenerator.generateFullTextIndexPath((FullTextIndex)object));
object.setStorageDescription(new FullTextIndexFileStorageDescription(object, path, FullTextIndexFileStorageFormat.identifier));
}
else { // Index or Sequence
object.setStorageDescription(getDefaultStorageDescription(object));
}
}
}
}