/* * JBoss, Home of Professional Open Source. * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. Some portions may be licensed * to Red Hat, Inc. under one or more contributor license agreements. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. */ package org.teiid.translator.infinispan.hotrod; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import org.infinispan.commons.api.BasicCache; import org.infinispan.query.remote.client.ProtobufMetadataManagerConstants; import org.teiid.core.util.ObjectConverterUtil; import org.teiid.infinispan.api.InfinispanConnection; import org.teiid.infinispan.api.ProtobufDataManager; import org.teiid.infinispan.api.ProtobufResource; import org.teiid.language.SQLConstants.Tokens; import org.teiid.logging.LogConstants; import org.teiid.logging.LogManager; import org.teiid.metadata.BaseColumn.NullType; import org.teiid.metadata.Column; import org.teiid.metadata.Column.SearchType; import org.teiid.metadata.ExtensionMetadataProperty; import org.teiid.metadata.MetadataFactory; import org.teiid.metadata.Table; import org.teiid.translator.MetadataProcessor; import org.teiid.translator.TranslatorException; import org.teiid.translator.TranslatorProperty; import org.teiid.translator.TranslatorProperty.PropertyType; import com.squareup.protoparser.DataType; import com.squareup.protoparser.EnumConstantElement; import com.squareup.protoparser.EnumElement; import com.squareup.protoparser.FieldElement; import com.squareup.protoparser.FieldElement.Label; import com.squareup.protoparser.MessageElement; import com.squareup.protoparser.ProtoFile; import com.squareup.protoparser.ProtoParser; import com.squareup.protoparser.TypeElement; public class ProtobufMetadataProcessor implements MetadataProcessor<InfinispanConnection> { //private static final String WRAPPING_DEFINITIONS_RES = "/org/infinispan/protostream/message-wrapping.proto"; @ExtensionMetadataProperty(applicable=Table.class, datatype=String.class, display="Merge Into Table", description="Declare the name of parent table that this table needs to be merged into.") public static final String MERGE = MetadataFactory.INFINISPAN_URI+"MERGE"; //$NON-NLS-1$ @ExtensionMetadataProperty(applicable=Table.class, datatype=String.class, display="Cache Name", description="Cache name to store the contents into") public static final String CACHE = MetadataFactory.INFINISPAN_URI+"CACHE"; //$NON-NLS-1$ @ExtensionMetadataProperty(applicable= {Table.class,Column.class}, datatype=String.class, display="Message Name", description="Message name this table or column represents") public static final String MESSAGE_NAME = MetadataFactory.INFINISPAN_URI+"MESSAGE_NAME"; //$NON-NLS-1$ @ExtensionMetadataProperty(applicable= Column.class, datatype=String.class, display="Protobuf Tag Number", description="Protobuf field tag number") public static final String TAG = MetadataFactory.INFINISPAN_URI+"TAG"; //$NON-NLS-1$ @ExtensionMetadataProperty(applicable = {Table.class, Column.class}, datatype=String.class, display="Protobuf Parent Tag Number", description="Protobuf field parent tag number in the case of complex document") public static final String PARENT_TAG = MetadataFactory.INFINISPAN_URI+"PARENT_TAG"; //$NON-NLS-1$ @ExtensionMetadataProperty(applicable= {Table.class, Column.class}, datatype=String.class, display="column's parent column name", description="Protobuf field parent column name in the case of complex document") public static final String PARENT_COLUMN_NAME = MetadataFactory.INFINISPAN_URI+"PARENT_COLUMN_NAME"; //$NON-NLS-1$ @ExtensionMetadataProperty(applicable=Column.class, datatype=String.class, display="Pseudo Column", description="Pseudo column for join purposes") public static final String PSEUDO = MetadataFactory.INFINISPAN_URI+"PSEUDO"; //$NON-NLS-1$ private String protoFilePath; private ProtobufResource protoResource; private String protobufName; @TranslatorProperty(display="Protobuf file path", category=PropertyType.IMPORT, description="Protobuf file path to load as the schema of this model") public String getProtoFilePath() { return protoFilePath; } public void setProtoFilePath(String path) { this.protoFilePath = path; } @TranslatorProperty(display="Protobuf Name", category=PropertyType.IMPORT, description="When loading the Protobuf contents from Infinispan, limit the import to this given protobuf name") public String getProtobufName() { return protobufName; } public void setProtobufName(String name) { this.protobufName = name; } @Override public void process(MetadataFactory metadataFactory, InfinispanConnection connection) throws TranslatorException { String protobufFile = getProtoFilePath(); String protoContents = null; if( protobufFile != null && !protobufFile.isEmpty()) { File f = new File(protobufFile); if(f == null || !f.exists() || !f.isFile()) { throw new TranslatorException(InfinispanPlugin.Event.TEIID25000, InfinispanPlugin.Util.gs(InfinispanPlugin.Event.TEIID25000, protobufFile)); } try { protoContents = ObjectConverterUtil.convertFileToString(f); } catch (IOException e) { throw new TranslatorException(e); } this.protoResource = new ProtobufResource(protobufFile, protoContents); toTeiidSchema(protobufFile, protoContents, metadataFactory); } else if( this.protobufName != null) { // Read from cache boolean added = false; BasicCache<Object, Object> metadataCache = connection .getCacheFactory().getCache(ProtobufMetadataManagerConstants.PROTOBUF_METADATA_CACHE_NAME); for (Object key : metadataCache.keySet()) { if (!this.protobufName.equalsIgnoreCase((String)key)) { continue; } protobufFile = (String)key; protoContents = (String)metadataCache.get(key); // read all the schemas toTeiidSchema(protobufFile, protoContents, metadataFactory); this.protoResource = new ProtobufResource(protobufFile, protoContents); added = true; break; } if (!added) { throw new TranslatorException(InfinispanPlugin.Event.TEIID25012, InfinispanPlugin.Util.gs(InfinispanPlugin.Event.TEIID25012, this.protobufName)); } } else if (this.protoResource != null) { toTeiidSchema(this.protoResource.getIdentifier(), this.protoResource.getContents(), metadataFactory); } else { // expand the error message throw new TranslatorException(InfinispanPlugin.Event.TEIID25011, InfinispanPlugin.Util.gs(InfinispanPlugin.Event.TEIID25011)); } } @SuppressWarnings(value = "unchecked") static <T> List<T> filter(List<? super TypeElement> input, Class<T> ofType) { List<T> ts = new LinkedList<>(); for (Object elem : input) { if (ofType.isAssignableFrom(elem.getClass())) { ts.add((T) elem); } } return ts; } private void toTeiidSchema(String name, String contents, MetadataFactory mf) throws TranslatorException { LogManager.logDetail(LogConstants.CTX_CONNECTOR, "Processing Proto file:", name, " with contents\n", contents); ProtoFile protoFile = ProtoParser.parse(name, contents); List<MessageElement> messageTypes = filter(protoFile.typeElements(), MessageElement.class); List<EnumElement> enumTypes = filter(protoFile.typeElements(), EnumElement.class); // add tables HashSet<String> deleteTables = new HashSet<>(); for (MessageElement messageElement:messageTypes) { addTable(mf, messageTypes, enumTypes, messageElement, null, deleteTables); } for (String tableName:deleteTables) { mf.getSchema().removeTable(tableName); } } private Table addTable(MetadataFactory mf, List<MessageElement> messageTypes, List<EnumElement> enumTypes, MessageElement messageElement, String columnPrefix, HashSet<String> ignoreTables) throws TranslatorException { String tableName = messageElement.name(); if (mf.getSchema().getTable(tableName) != null) { return mf.getSchema().getTable(tableName); } if (ignoreTables.contains(tableName)) { return null; } Table table = mf.addTable(tableName); table.setSupportsUpdate(true); table.setNameInSource(messageElement.qualifiedName()); table.setAnnotation(messageElement.documentation()); for (FieldElement fieldElement:messageElement.fields()) { addColumn(mf, messageTypes, enumTypes, columnPrefix, table, fieldElement, ignoreTables, false); } return table; } private Column addColumn(MetadataFactory mf, List<MessageElement> messageTypes, List<EnumElement> enumTypes, String parentTableColumn, Table table, FieldElement fieldElement, HashSet<String> ignoreTables, boolean nested) throws TranslatorException { DataType type = fieldElement.type(); String annotation = fieldElement.documentation(); String columnName = fieldElement.name(); String teiidType = null; if (isEnum(messageTypes, enumTypes, type)) { teiidType = ProtobufDataManager.teiidType(type, isCollection(fieldElement), true); } else if (isMessage(messageTypes, type)) { // this is nested table. If the nested table has PK, then we will configure external // if not we will consider this as embedded with primary table. String nestedName = ((DataType.NamedType)type).name(); MessageElement nestedMessageElement = getMessage(messageTypes, nestedName); if (nestedMessageElement == null) { throw new TranslatorException(InfinispanPlugin.Event.TEIID25001, InfinispanPlugin.Util.gs(InfinispanPlugin.Event.TEIID25001, nestedName, columnName)); } // this is one-2-many if (isCollection(fieldElement)) { Table nestedTable = addTable(mf, messageTypes, enumTypes, nestedMessageElement, parentTableColumn == null ? columnName : parentTableColumn + Tokens.DOT + columnName, ignoreTables); if (table.getPrimaryKey() != null) { // add additional column to represent the relationship Column parentColumn = table.getPrimaryKey().getColumns().get(0); String psedoColumnName = table.getName()+"_"+parentColumn.getName(); Column addedColumn = mf.addColumn(psedoColumnName, parentColumn.getRuntimeType(), nestedTable); addedColumn.setNameInSource(parentColumn.getName()); addedColumn.setUpdatable(true); addedColumn.setProperty(PSEUDO, columnName); addedColumn.setSearchType(SearchType.Searchable); List<String> keyColumns = new ArrayList<String>(); keyColumns.add(addedColumn.getName()); List<String> refColumns = new ArrayList<String>(); refColumns.add(parentColumn.getName()); mf.addForiegnKey("FK_"+table.getName().toUpperCase(), keyColumns, refColumns, table.getName(), nestedTable); //$NON-NLS-1$ // since this nested table can not be reached directly, put a access // pattern on it. nestedTable.setProperty(MERGE, table.getFullName()); nestedTable.setProperty(PARENT_TAG, Integer.toString(fieldElement.tag())); nestedTable.setProperty(PARENT_COLUMN_NAME, columnName); } else { ignoreTables.add(nestedName); LogManager.logInfo(LogConstants.CTX_CONNECTOR, InfinispanPlugin.Util.gs(InfinispanPlugin.Event.TEIID25006, nestedName)); } } else { ignoreTables.add(nestedMessageElement.name()); // inline all the columns from the message and return for (FieldElement nestedElement:nestedMessageElement.fields()) { Column nestedColumn = addColumn(mf, messageTypes, enumTypes, columnName, table, nestedElement, ignoreTables, true); nestedColumn.setNameInSource(nestedElement.name()); nestedColumn.setProperty(MESSAGE_NAME, nestedMessageElement.qualifiedName()); nestedColumn.setProperty(PARENT_TAG, Integer.toString(fieldElement.tag())); nestedColumn.setProperty(PARENT_COLUMN_NAME, columnName); } } return null; } else { teiidType = ProtobufDataManager.teiidType(type, isCollection(fieldElement), false); } Column c = null; if (nested) { c = mf.addColumn(parentTableColumn + "_" + columnName, teiidType, table); } else { c = mf.addColumn(columnName, teiidType, table); } c.setNativeType(fieldElement.type().toString()); c.setUpdatable(true); c.setNullType(fieldElement.label() == Label.REQUIRED ? NullType.No_Nulls : NullType.Nullable); c.setProperty(TAG, Integer.toString(fieldElement.tag())); // process default value if (fieldElement.getDefault() != null) { if (isEnum(messageTypes, enumTypes, type)) { String ordinal = getEnumOrdinal(messageTypes, enumTypes,((DataType.NamedType) type).name(), fieldElement.getDefault().value().toString()); if (ordinal != null) { c.setDefaultValue(ordinal); } } else { c.setDefaultValue(fieldElement.getDefault().value().toString()); } } // process annotations if (table.getAnnotation() != null) { if (table.getAnnotation().contains("@Indexed")) { c.setSearchType(SearchType.Searchable); } } if ( annotation != null && !annotation.isEmpty()) { c.setAnnotation(annotation); if(annotation.contains("@IndexedField(index=false")) { c.setSearchType(null); } if(annotation.contains("@Id")) { List<String> pkNames = new ArrayList<String>(); pkNames.add(fieldElement.name()); mf.addPrimaryKey("PK_"+fieldElement.name().toUpperCase(), pkNames, table); } } return c; } private boolean isCollection(FieldElement fieldElement) { return fieldElement.label() == Label.REPEATED; } private String getEnumOrdinal(List<MessageElement> messageTypes, List<EnumElement> enumTypes, String name, String value) { for (EnumElement element:enumTypes) { if (element.name().equals(name)) { for(EnumConstantElement constant:element.constants()) { if (constant.name().equals(value)) { return String.valueOf(constant.tag()); } } } } // enum does not nest, messages nest for (MessageElement element:messageTypes) { List<MessageElement> childMessageTypes = filter(element.nestedElements(), MessageElement.class); List<EnumElement> childEnumTypes = filter(element.nestedElements(), EnumElement.class); String child = getEnumOrdinal(childMessageTypes, childEnumTypes, name, value); if (child != null) { return child; } } return null; } private boolean isEnum(List<MessageElement> messageTypes, List<EnumElement> enumTypes, DataType type) { if (type instanceof DataType.NamedType) { for (EnumElement element:enumTypes) { if (element.name().equals(((DataType.NamedType)type).name())) { return true; } } // enum does not nest, messages nest for (MessageElement element:messageTypes) { List<MessageElement> childMessageTypes = filter(element.nestedElements(), MessageElement.class); List<EnumElement> childEnumTypes = filter(element.nestedElements(), EnumElement.class); if (isEnum(childMessageTypes, childEnumTypes, type)) { return true; } } } return false; } private boolean isMessage(List<MessageElement> messageTypes, DataType type) { if (type instanceof DataType.NamedType) { for (MessageElement element:messageTypes) { if (element.name().equals(((DataType.NamedType)type).name())) { return true; } // check also nested messages List<MessageElement> childMessageTypes = filter(element.nestedElements(), MessageElement.class); if (isMessage(childMessageTypes, type)) { return true; } } } return false; } private MessageElement getMessage(List<MessageElement> messageTypes, String name) { for (MessageElement element : messageTypes) { if (element.name().equals(name)) { return element; } List<MessageElement> childMessageTypes = filter(element.nestedElements(), MessageElement.class); MessageElement child = getMessage(childMessageTypes, name); if (child != null) { return child; } } return null; } public ProtobufResource getProtobufResource() { return this.protoResource; } public void setProtobufResource(ProtobufResource resource) { this.protoResource = resource; } static String getPseudo(Column column) { return column.getProperty(PSEUDO, false); } static boolean isPseudo(Column column) { return (column.getProperty(PSEUDO, false) != null); } static String getMessageName(Table table) { if (table.getNameInSource() != null) { return table.getNameInSource(); } return table.getName(); } static String getMessageName(Column column) { return column.getProperty(MESSAGE_NAME, false); } static String getMerge(Table table) { return table.getProperty(MERGE, false); } static int getTag(Column column) { if (column.getProperty(TAG, false) != null) { return Integer.parseInt(column.getProperty(TAG, false)); } return -1; } static int getParentTag(Column column) { if (column.getProperty(PARENT_TAG, false) != null) { return Integer.parseInt(column.getProperty(PARENT_TAG, false)); } return -1; } static int getParentTag(Table table) { if (table.getProperty(PARENT_TAG, false) != null) { return Integer.parseInt(table.getProperty(PARENT_TAG, false)); } return -1; } static String getParentColumnName(Column column) { return column.getProperty(PARENT_COLUMN_NAME, false); } static String getParentColumnName(Table table) { return table.getProperty(PARENT_COLUMN_NAME, false); } }