/* * 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.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.teiid.infinispan.api.InfinispanDocument; import org.teiid.infinispan.api.TableWireFormat; import org.teiid.language.*; import org.teiid.language.SQLConstants.Tokens; import org.teiid.metadata.Column; import org.teiid.metadata.RuntimeMetadata; import org.teiid.metadata.Table; import org.teiid.translator.TranslatorException; public class InfinispanUpdateVisitor extends IckleConversionVisitor { protected enum OperationType {INSERT, UPDATE, DELETE, UPSERT}; protected ArrayList<TranslatorException> exceptions = new ArrayList<TranslatorException>(); private OperationType operationType; private InfinispanDocument insertPayload; private Map<String, Object> updatePayload = new HashMap<>(); private Object identity; private Condition whereClause; public InfinispanUpdateVisitor(RuntimeMetadata metadata) { super(metadata, true); } public Object getIdentity() { return identity; } public OperationType getOperationType() { return this.operationType; } public InfinispanDocument getInsertPayload() { return insertPayload; } public Map<String, Object> getUpdatePayload() { return updatePayload; } @Override public void visit(Insert obj) { this.operationType = OperationType.INSERT; if (obj.isUpsert()) { this.operationType = OperationType.UPSERT; } visitNode(obj.getTable()); Column pkColumn = getPrimaryKey(); if (pkColumn == null) { this.exceptions.add(new TranslatorException( InfinispanPlugin.Util.gs(InfinispanPlugin.Event.TEIID25013, getParentTable().getName()))); return; } // table that insert issued for Table table = obj.getTable().getMetadataObject(); try { // create the top table parent document, where insert is actually being done at InfinispanDocument targetDocument = buildTargetDocument(table, true); // build the payload object from insert int elementCount = obj.getColumns().size(); for (int i = 0; i < elementCount; i++) { ColumnReference columnReference = obj.getColumns().get(i); Column column = columnReference.getMetadataObject(); this.projectedExpressions.add(columnReference); List<Expression> values = ((ExpressionValueSource)obj.getValueSource()).getValues(); Expression expr = values.get(i); Object value = resolveExpressionValue(expr); updateDocument(targetDocument, column, value); if (column.equals(pkColumn) || pkColumn.equals(normalizePseudoColumn(column))) { this.identity = value; } } this.insertPayload = targetDocument; } catch (NumberFormatException e) { this.exceptions.add(new TranslatorException(e)); } catch (TranslatorException e) { this.exceptions.add(new TranslatorException(e)); } if (this.identity == null) { this.exceptions.add(new TranslatorException( InfinispanPlugin.Util.gs(InfinispanPlugin.Event.TEIID25004, getParentTable().getName()))); } } @SuppressWarnings("unchecked") private void updateDocument(InfinispanDocument parentDocument, Column column, Object value) throws TranslatorException { boolean complexObject = this.nested; InfinispanDocument targetDocument = parentDocument; int parentTag = ProtobufMetadataProcessor.getParentTag(column); if (parentTag != -1) { // this is in one-2-one case. Dummy child will be there due to buildTargetDocument logic. String messageName = ProtobufMetadataProcessor.getMessageName(column); InfinispanDocument child = (InfinispanDocument)parentDocument.getChildDocuments(messageName).get(0); targetDocument = child; complexObject = true; } else if (this.nested){ Table table = (Table)column.getParent(); String messageName = ProtobufMetadataProcessor.getMessageName(table); InfinispanDocument child = (InfinispanDocument)parentDocument.getChildDocuments(messageName).get(0); targetDocument = child; complexObject = true; } if (!ProtobufMetadataProcessor.isPseudo(column)) { if (value instanceof List) { List<Object> l = (List<Object>)value; for(Object o : l) { targetDocument.addArrayProperty(getName(column), o); } } else { targetDocument.addProperty(getName(column), value); } String attrName = MarshallerBuilder.getDocumentAttributeName(column, complexObject, this.metadata); this.updatePayload.put(attrName, value); } } private InfinispanDocument buildTargetDocument(Table table, boolean addDefaults) throws TranslatorException { TreeMap<Integer, TableWireFormat> wireMap = MarshallerBuilder.getWireMap(getParentTable(), metadata); String messageName = ProtobufMetadataProcessor.getMessageName(getParentTable()); InfinispanDocument parentDocument = new InfinispanDocument(messageName, wireMap, null); // if there are any one-2-one relation build them and add defaults addDefaults(parentDocument, getParentTable(), addDefaults); // now create the document at child node, this is one-2-many case if (!table.equals(getParentTable())) { messageName = ProtobufMetadataProcessor.getMessageName(table); int parentTag = ProtobufMetadataProcessor.getParentTag(table); TableWireFormat twf = wireMap.get(TableWireFormat.buildNestedTag(parentTag)); this.nested = true; InfinispanDocument child = new InfinispanDocument(messageName, twf.getNestedWireMap(), parentDocument); addDefaults(child, table, addDefaults); parentDocument.addChildDocument(messageName, child); } return parentDocument; } private void addDefaults(InfinispanDocument parentDocument, Table table, boolean addDefaults) throws TranslatorException { for (Column column : table.getColumns()) { int parentTag = ProtobufMetadataProcessor.getParentTag(column); if (parentTag != -1) { String messageName = ProtobufMetadataProcessor.getMessageName(column); List<?> children = parentDocument.getChildDocuments(messageName); InfinispanDocument child = null; if (children == null || children.isEmpty()) { TableWireFormat twf = parentDocument.getWireMap().get(TableWireFormat.buildNestedTag(parentTag)); child = new InfinispanDocument(messageName, twf.getNestedWireMap(), parentDocument); parentDocument.addChildDocument(messageName, child); } else { child = (InfinispanDocument)children.get(0); } if (addDefaults && column.getDefaultValue() != null) { child.addProperty(getName(column), column.getDefaultValue()); } } else { if (addDefaults && column.getDefaultValue() != null) { parentDocument.addProperty(getName(column), column.getDefaultValue()); } } } } public Column getPrimaryKey() { Column pkColumn = null; if (getParentTable().getPrimaryKey() != null) { pkColumn = getParentTable().getPrimaryKey().getColumns().get(0); } return pkColumn; } private Object resolveExpressionValue(Expression expr) { Object value = null; if (expr instanceof Literal) { value = ((Literal)expr).getValue(); } else if (expr instanceof org.teiid.language.Array) { org.teiid.language.Array contents = (org.teiid.language.Array)expr; List<Expression> arrayExprs = contents.getExpressions(); List<Object> values = new ArrayList<Object>(); for (Expression exp:arrayExprs) { if (exp instanceof Literal) { values.add(((Literal)exp).getValue()); } else { this.exceptions.add(new TranslatorException(InfinispanPlugin.Util.gs(InfinispanPlugin.Event.TEIID25003))); } } value = values; } else { this.exceptions.add(new TranslatorException(InfinispanPlugin.Util.gs(InfinispanPlugin.Event.TEIID25003))); } return value; } @Override public void visit(Update obj) { this.operationType = OperationType.UPDATE; append(obj.getTable()); if (obj.getWhere() != null) { buffer.append(Tokens.SPACE).append(SQLConstants.Reserved.WHERE).append(Tokens.SPACE); append(obj.getWhere()); // Can't use the original where string because it is designed for the document model querying this.whereClause = obj.getWhere(); } // table that update issued for Table table = obj.getTable().getMetadataObject(); if (!table.equals(getParentTable())) { this.nested = true; } // read the properties try { InfinispanDocument targetDocument = buildTargetDocument(table, false); int elementCount = obj.getChanges().size(); for (int i = 0; i < elementCount; i++) { Column column = obj.getChanges().get(i).getSymbol().getMetadataObject(); Expression expr = obj.getChanges().get(i).getValue(); Object value = resolveExpressionValue(expr); //this.updatePayload.put(getName(column), value); updateDocument(targetDocument, column, value); } this.insertPayload = targetDocument; } catch (TranslatorException e) { this.exceptions.add(e); } } @Override public void visit(Delete obj) { this.operationType = OperationType.DELETE; append(obj.getTable()); // table that update issued for Table table = obj.getTable().getMetadataObject(); if (!table.equals(getParentTable())) { this.nested = true; } if (obj.getWhere() != null) { buffer.append(Tokens.SPACE).append(SQLConstants.Reserved.WHERE).append(Tokens.SPACE); append(obj.getWhere()); this.whereClause = obj.getWhere(); } } public List<String> getProjectedColumnNames() { ArrayList<String> names = new ArrayList<>(); for (Expression expr: this.projectedExpressions) { if (expr instanceof ColumnReference) { names.add(((ColumnReference)expr).getMetadataObject().getName()); } else if (expr instanceof Function) { names.add(((Function)expr).getName()); } } return names; } public String getUpdateQuery() { StringBuilder sb = new StringBuilder(); sb.append(SQLConstants.Reserved.FROM); sb.append(Tokens.SPACE).append(super.toString()); return sb.toString(); } public String getDeleteQuery() { StringBuilder sb = new StringBuilder(); if (!isNestedOperation()) { addSelectedColumns(sb); sb.append(Tokens.SPACE); } sb.append(SQLConstants.Reserved.FROM); sb.append(Tokens.SPACE).append(super.toString()); return sb.toString(); } Condition getWhereClause() { return whereClause; } }